Surface.cs 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435
  1. using PaintDotNet.SystemLayer;
  2. using System;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Drawing.Imaging;
  6. namespace PaintDotNet
  7. {
  8. /// <summary>
  9. /// This is our Surface type. We allocate our own blocks of memory for this,
  10. /// and provide ways to create a GDI+ Bitmap object that aliases our surface.
  11. /// That way we can do everything fast, in memory and have complete control,
  12. /// and still have the ability to use GDI+ for drawing and rendering where
  13. /// appropriate.
  14. /// </summary>
  15. [Serializable]
  16. public sealed class Surface : IDisposable, ICloneable
  17. {
  18. /// <summary>
  19. /// 图像内存块
  20. /// </summary>
  21. private MemoryBlock scan0;
  22. /// <summary>
  23. /// 宽
  24. /// </summary>
  25. private int width;
  26. /// <summary>
  27. /// 高
  28. /// </summary>
  29. private int height;
  30. /// <summary>
  31. /// 扫描宽度
  32. /// </summary>
  33. private int stride;
  34. /// <summary>
  35. /// 释放标记
  36. /// </summary>
  37. private bool disposed = false;
  38. /// <summary>
  39. /// 存储打开时图片的格式
  40. /// </summary>
  41. private PixelFormat pixelFormat = PixelFormat.Format32bppArgb;
  42. /// <summary>
  43. /// 缩略图
  44. /// </summary>
  45. private Bitmap thumbnail;
  46. /// <summary>
  47. /// 原图的备份
  48. /// </summary>
  49. private Bitmap thumborigin;
  50. /// <summary>
  51. /// 创建原图的备份
  52. /// </summary>
  53. public void CreateThumborigin()
  54. {
  55. this.thumborigin = CreateAliasedBitmap();
  56. }
  57. public Bitmap Thumborigin
  58. {
  59. get
  60. {
  61. return this.thumborigin;
  62. }
  63. set
  64. {
  65. this.thumborigin = value;
  66. }
  67. }
  68. public Bitmap Thumbnail
  69. {
  70. get
  71. {
  72. return this.thumbnail;
  73. }
  74. }
  75. /// <summary>
  76. /// 创建缩略图
  77. /// </summary>
  78. public void CreateThumbnail()
  79. {
  80. Bitmap origin = CreateAliasedBitmap();
  81. Bitmap bitmap = null;
  82. if (origin.Width > origin.Height)
  83. {
  84. bitmap = MakeThumbnail(CreateAliasedBitmap(), 90, 90, "W");
  85. }
  86. else if (origin.Height > origin.Width)
  87. {
  88. bitmap = MakeThumbnail(CreateAliasedBitmap(), 90, 90, "H");
  89. }
  90. else
  91. {
  92. bitmap = MakeThumbnail(CreateAliasedBitmap(), 90, 90, "W");
  93. }
  94. this.thumbnail = bitmap;
  95. }
  96. public PixelFormat PixelFormat
  97. {
  98. get
  99. {
  100. return this.pixelFormat;
  101. }
  102. set
  103. {
  104. this.pixelFormat = value;
  105. }
  106. }
  107. public bool IsDisposed
  108. {
  109. get
  110. {
  111. return this.disposed;
  112. }
  113. }
  114. /// <summary>
  115. /// Gets a MemoryBlock which is the buffer holding the pixels associated
  116. /// with this Surface.
  117. /// </summary>
  118. public MemoryBlock Scan0
  119. {
  120. get
  121. {
  122. if (this.disposed)
  123. {
  124. throw new ObjectDisposedException("Surface");
  125. }
  126. return this.scan0;
  127. }
  128. }
  129. /// <summary>
  130. /// Gets the width, in pixels, of this Surface.
  131. /// </summary>
  132. /// <remarks>
  133. /// This property will never throw an ObjectDisposedException.
  134. /// </remarks>
  135. public int Width
  136. {
  137. get
  138. {
  139. return this.width;
  140. }
  141. }
  142. /// <summary>
  143. /// Gets the height, in pixels, of this Surface.
  144. /// </summary>
  145. /// <remarks>
  146. /// This property will never throw an ObjectDisposedException.
  147. /// </remarks>
  148. public int Height
  149. {
  150. get
  151. {
  152. return this.height;
  153. }
  154. }
  155. /// <summary>
  156. /// Gets the stride, in bytes, for this Surface.
  157. /// </summary>
  158. /// <remarks>
  159. /// Stride is defined as the number of bytes between the beginning of a row and
  160. /// the beginning of the next row. Thus, in loose C notation: stride = (byte *)&this[0, 1] - (byte *)&this[0, 0].
  161. /// Stride will always be equal to <b>or greater than</b> Width * ColorBgra.SizeOf.
  162. /// This property will never throw an ObjectDisposedException.
  163. /// </remarks>
  164. public int Stride
  165. {
  166. get
  167. {
  168. return this.stride;
  169. }
  170. }
  171. /// <summary>
  172. /// Gets the size, in pixels, of this Surface.
  173. /// </summary>
  174. /// <remarks>
  175. /// This is a convenience function that creates a new Size instance based
  176. /// on the values of the Width and Height properties.
  177. /// This property will never throw an ObjectDisposedException.
  178. /// </remarks>
  179. public Size Size
  180. {
  181. get
  182. {
  183. return new Size(this.width, this.height);
  184. }
  185. }
  186. /// <summary>
  187. /// Gets the bounds of this Surface, in pixels.
  188. /// </summary>
  189. /// <remarks>
  190. /// This is a convenience function that returns Rectangle(0, 0, Width, Height).
  191. /// This property will never throw an ObjectDisposedException.
  192. /// </remarks>
  193. public Rectangle Bounds
  194. {
  195. get
  196. {
  197. return new Rectangle(0, 0, width, height);
  198. }
  199. }
  200. /// <summary>
  201. /// Creates a new instance of the Surface class.
  202. /// </summary>
  203. /// <param name="size">The size, in pixels, of the new Surface.</param>
  204. public Surface(Size size)
  205. : this(size.Width, size.Height)
  206. {
  207. }
  208. public Surface(Size size, PixelFormat pixelFormat)
  209. : this(size.Width, size.Height, pixelFormat)
  210. {
  211. }
  212. /// <summary>
  213. /// Creates a new instance of the Surface class.
  214. /// </summary>
  215. /// <param name="width">The width, in pixels, of the new Surface.</param>
  216. /// <param name="height">The height, in pixels, of the new Surface.</param>
  217. public Surface(int width, int height)
  218. {
  219. int stride;
  220. long bytes;
  221. try
  222. {
  223. stride = checked(width * ColorBgra.SizeOf);
  224. bytes = (long)height * (long)stride;
  225. }
  226. catch (OverflowException ex)
  227. {
  228. throw new OutOfMemoryException("Dimensions are too large - not enough memory, width=" + width.ToString() + ", height=" + height.ToString(), ex);
  229. }
  230. MemoryBlock scan0 = new MemoryBlock(width, height);
  231. Create(width, height, stride, scan0);
  232. }
  233. public Surface(int width, int height, PixelFormat pixelFormat)
  234. {
  235. int ch;
  236. PixelFormat = pixelFormat;
  237. try
  238. {
  239. if (pixelFormat == PixelFormat.Format24bppRgb)
  240. ch = 3;
  241. else if (pixelFormat == PixelFormat.Format8bppIndexed)
  242. ch = 1;
  243. else
  244. ch = 4;
  245. }
  246. catch (OverflowException ex)
  247. {
  248. throw new OutOfMemoryException("Dimensions are too large - not enough memory, width=" + width.ToString() + ", height=" + height.ToString(), ex);
  249. }
  250. MemoryBlock scan0 = new MemoryBlock(width, height, ch);
  251. Create(width, height, width * ch, scan0);
  252. }
  253. /// <summary>
  254. /// Creates a new instance of the Surface class that reuses a block of memory that was previously allocated.
  255. /// </summary>
  256. /// <param name="width">The width, in pixels, for the Surface.</param>
  257. /// <param name="height">The height, in pixels, for the Surface.</param>
  258. /// <param name="stride">The stride, in bytes, for the Surface.</param>
  259. /// <param name="scan0">The MemoryBlock to use. The beginning of this buffer defines the upper left (0, 0) pixel of the Surface.</param>
  260. private Surface(int width, int height, int stride, MemoryBlock scan0)
  261. {
  262. Create(width, height, stride, scan0);
  263. }
  264. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "width")]
  265. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "height")]
  266. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "stride")]
  267. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "scan0")]
  268. private void Create(int width, int height, int stride, MemoryBlock scan0)
  269. {
  270. this.width = width;
  271. this.height = height;
  272. this.stride = stride;
  273. this.scan0 = scan0;
  274. }
  275. ~Surface()
  276. {
  277. Dispose(false);
  278. }
  279. /// <summary>
  280. /// Creates a Surface that aliases a portion of this Surface.
  281. /// </summary>
  282. /// <param name="bounds">The portion of this Surface that will be aliased.</param>
  283. /// <remarks>The upper left corner of the new Surface will correspond to the
  284. /// upper left corner of this rectangle in the original Surface.</remarks>
  285. /// <returns>A Surface that aliases the requested portion of this Surface.</returns>
  286. public Surface CreateWindow(Rectangle bounds)
  287. {
  288. return CreateWindow(bounds.X, bounds.Y, bounds.Width, bounds.Height);
  289. }
  290. public Surface CreateWindow(int x, int y, int windowWidth, int windowHeight)
  291. {
  292. if (disposed)
  293. {
  294. throw new ObjectDisposedException("Surface");
  295. }
  296. if (windowHeight == 0)
  297. {
  298. throw new ArgumentOutOfRangeException("windowHeight", "must be greater than zero");
  299. }
  300. Rectangle original = this.Bounds;
  301. Rectangle sub = new Rectangle(x, y, windowWidth, windowHeight);
  302. Rectangle clipped = Rectangle.Intersect(original, sub);
  303. if (clipped != sub)
  304. {
  305. throw new ArgumentOutOfRangeException("bounds", new Rectangle(x, y, windowWidth, windowHeight),
  306. "bounds parameters must be a subset of this Surface's bounds");
  307. }
  308. long offset = ((long)stride * (long)y) + ((long)ColorBgra.SizeOf * (long)x);
  309. long length = ((windowHeight - 1) * (long)stride) + (long)windowWidth * (long)ColorBgra.SizeOf;
  310. MemoryBlock block = new MemoryBlock(this.scan0, offset, length);
  311. return new Surface(windowWidth, windowHeight, this.stride, block);
  312. }
  313. /// <summary>
  314. /// Gets the offset, in bytes, of the requested row from the start of the surface.
  315. /// </summary>
  316. /// <param name="y">The row.</param>
  317. /// <returns>The number of bytes between (0,0) and (0,y).</returns>
  318. public long GetRowByteOffset(int y)
  319. {
  320. if (y < 0 || y >= height)
  321. {
  322. throw new ArgumentOutOfRangeException("y", "Out of bounds: y=" + y.ToString());
  323. }
  324. return (long)y * (long)stride;
  325. }
  326. /// <summary>
  327. /// Gets the offset, in bytes, of the requested row from the start of the surface.
  328. /// </summary>
  329. /// <param name="y">The row.</param>
  330. /// <returns>The number of bytes between (0,0) and (0,y)</returns>
  331. /// <remarks>
  332. /// This method does not do any bounds checking and is potentially unsafe to use,
  333. /// but faster than GetRowByteOffset().
  334. /// </remarks>
  335. public unsafe long GetRowByteOffsetUnchecked(int y)
  336. {
  337. #if DEBUG
  338. if (y < 0 || y >= this.height)
  339. {
  340. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  341. }
  342. #endif
  343. return (long)y * (long)stride;
  344. }
  345. /// <summary>
  346. /// Gets a pointer to the beginning of the requested row in the surface.
  347. /// </summary>
  348. /// <param name="y">The row</param>
  349. /// <returns>A pointer that references (0,y) in this surface.</returns>
  350. /// <remarks>Since this returns a pointer, it is potentially unsafe to use.</remarks>
  351. public unsafe ColorBgra* GetRowAddress(int y)
  352. {
  353. return (ColorBgra*)(((byte*)scan0.VoidStar) + GetRowByteOffset(y));
  354. }
  355. /// <summary>
  356. /// Gets a pointer to the beginning of the requested row in the surface.
  357. /// </summary>
  358. /// <param name="y">The row</param>
  359. /// <returns>A pointer that references (0,y) in this surface.</returns>
  360. /// <remarks>
  361. /// This method does not do any bounds checking and is potentially unsafe to use,
  362. /// but faster than GetRowAddress().
  363. /// </remarks>
  364. public unsafe ColorBgra* GetRowAddressUnchecked(int y)
  365. {
  366. #if DEBUG
  367. if (y < 0 || y >= this.height)
  368. {
  369. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  370. }
  371. #endif
  372. return (ColorBgra*)(((byte*)scan0.VoidStar) + GetRowByteOffsetUnchecked(y));
  373. }
  374. /// <summary>
  375. /// Gets the number of bytes from the beginning of a row to the requested column.
  376. /// </summary>
  377. /// <param name="x">The column.</param>
  378. /// <returns>
  379. /// The number of bytes between (0,n) and (x,n) where n is in the range [0, Height).
  380. /// </returns>
  381. /// <remarks>
  382. /// This method does not do any bounds checking and is potentially unsafe to use,
  383. /// but faster than GetColumnByteOffset().
  384. /// </remarks>
  385. public long GetColumnByteOffsetUnchecked(int x)
  386. {
  387. #if DEBUG
  388. if (x < 0 || x >= this.width)
  389. {
  390. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  391. }
  392. #endif
  393. return (long)x * (long)ColorBgra.SizeOf;
  394. }
  395. /// <summary>
  396. /// Gets the number of bytes from the beginning of the surface's buffer to
  397. /// the requested point.
  398. /// </summary>
  399. /// <param name="x">The x offset.</param>
  400. /// <param name="y">The y offset.</param>
  401. /// <returns>
  402. /// The number of bytes between (0,0) and (x,y).
  403. /// </returns>
  404. /// <remarks>
  405. /// This method does not do any bounds checking and is potentially unsafe to use,
  406. /// but faster than GetPointByteOffset().
  407. /// </remarks>
  408. public long GetPointByteOffsetUnchecked(int x, int y)
  409. {
  410. #if DEBUG
  411. if (x < 0 || x >= this.width)
  412. {
  413. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  414. }
  415. if (y < 0 || y >= this.height)
  416. {
  417. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  418. }
  419. #endif
  420. return GetRowByteOffsetUnchecked(y) + GetColumnByteOffsetUnchecked(x);
  421. }
  422. /// <summary>
  423. /// Gets the color at a specified point in the surface.
  424. /// </summary>
  425. /// <param name="x">The x offset.</param>
  426. /// <param name="y">The y offset.</param>
  427. /// <returns>The color at the requested location.</returns>
  428. public ColorBgra GetPoint(int x, int y)
  429. {
  430. return this[x, y];
  431. }
  432. /// <summary>
  433. /// Gets the address in memory of the requested point.
  434. /// </summary>
  435. /// <param name="x">The x offset.</param>
  436. /// <param name="y">The y offset.</param>
  437. /// <returns>A pointer to the requested point in the surface.</returns>
  438. /// <remarks>Since this method returns a pointer, it is potentially unsafe to use.</remarks>
  439. public unsafe ColorBgra* GetPointAddress(int x, int y)
  440. {
  441. if (x < 0 || x >= Width)
  442. {
  443. throw new ArgumentOutOfRangeException("x", "Out of bounds: x=" + x.ToString());
  444. }
  445. return GetRowAddress(y) + x;
  446. }
  447. /// <summary>
  448. /// Gets the address in memory of the requested point.
  449. /// </summary>
  450. /// <param name="pt">The point to retrieve.</param>
  451. /// <returns>A pointer to the requested point in the surface.</returns>
  452. /// <remarks>Since this method returns a pointer, it is potentially unsafe to use.</remarks>
  453. public unsafe ColorBgra* GetPointAddress(Point pt)
  454. {
  455. return GetPointAddress(pt.X, pt.Y);
  456. }
  457. /// <summary>
  458. /// Gets the address in memory of the requested point.
  459. /// </summary>
  460. /// <param name="x">The x offset.</param>
  461. /// <param name="y">The y offset.</param>
  462. /// <returns>A pointer to the requested point in the surface.</returns>
  463. /// <remarks>
  464. /// This method does not do any bounds checking and is potentially unsafe to use,
  465. /// but faster than GetPointAddress().
  466. /// </remarks>
  467. public unsafe ColorBgra* GetPointAddressUnchecked(int x, int y)
  468. {
  469. #if DEBUG
  470. if (x < 0 || x >= this.width)
  471. {
  472. Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")");
  473. }
  474. if (y < 0 || y >= this.height)
  475. {
  476. Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")");
  477. }
  478. #endif
  479. return unchecked(x + (ColorBgra*)(((byte*)scan0.VoidStar) + (y * stride)));
  480. }
  481. /// <summary>
  482. /// Gets a MemoryBlock that references the row requested.
  483. /// </summary>
  484. /// <param name="y">The row.</param>
  485. /// <returns>A MemoryBlock that gives access to the bytes in the specified row.</returns>
  486. /// <remarks>This method is the safest to use for direct memory access to a row's pixel data.</remarks>
  487. public MemoryBlock GetRow(int y)
  488. {
  489. return new MemoryBlock(scan0, GetRowByteOffset(y), (long)width * (long)ColorBgra.SizeOf);
  490. }
  491. /// <summary>
  492. /// Determines if the requested pixel coordinate is within bounds.
  493. /// </summary>
  494. /// <param name="x">The x coordinate.</param>
  495. /// <param name="y">The y coordinate.</param>
  496. /// <returns>true if (x,y) is in bounds, false if it's not.</returns>
  497. public bool IsVisible(int x, int y)
  498. {
  499. return x >= 0 && x < width && y >= 0 && y < height;
  500. }
  501. /// <summary>
  502. /// Determines if the requested row offset is within bounds.
  503. /// </summary>
  504. /// <param name="y">The row.</param>
  505. /// <returns>true if y &gt;= 0 and y &lt; height, otherwise false</returns>
  506. public bool IsRowVisible(int y)
  507. {
  508. return y >= 0 && y < Height;
  509. }
  510. /// <summary>
  511. /// Determines if the requested column offset is within bounds.
  512. /// </summary>
  513. /// <param name="x">The column.</param>
  514. /// <returns>true if x &gt;= 0 and x &lt; width, otherwise false.</returns>
  515. public bool IsColumnVisible(int x)
  516. {
  517. return x >= 0 && x < Width;
  518. }
  519. /// <summary>
  520. /// Gets or sets the pixel value at the requested offset.
  521. /// </summary>
  522. /// <remarks>
  523. /// This property is implemented with correctness and error checking in mind. If performance
  524. /// is a concern, do not use it.
  525. /// </remarks>
  526. public ColorBgra this[int x, int y]
  527. {
  528. get
  529. {
  530. if (disposed)
  531. {
  532. throw new ObjectDisposedException("Surface");
  533. }
  534. if (x < 0 || y < 0 || x >= this.width || y >= this.height)
  535. {
  536. throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString());
  537. }
  538. unsafe
  539. {
  540. return *GetPointAddressUnchecked(x, y);
  541. }
  542. }
  543. set
  544. {
  545. if (disposed)
  546. {
  547. throw new ObjectDisposedException("Surface");
  548. }
  549. if (x < 0 || y < 0 || x >= this.width || y >= this.height)
  550. {
  551. throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString());
  552. }
  553. unsafe
  554. {
  555. *GetPointAddressUnchecked(x, y) = value;
  556. }
  557. }
  558. }
  559. /// <summary>
  560. /// Helper function. Same as calling CreateAliasedBounds(Bounds).
  561. /// </summary>
  562. /// <returns>A GDI+ Bitmap that aliases the entire Surface.</returns>
  563. public Bitmap CreateAliasedBitmap(bool alpha)
  564. {
  565. return CreateAliasedBitmap(this.Bounds, alpha);
  566. }
  567. /// <summary>
  568. /// Helper function. Same as calling CreateAliasedBounds(Bounds).
  569. /// </summary>
  570. /// <returns>A GDI+ Bitmap that aliases the entire Surface.</returns>
  571. public Bitmap CreateAliasedBitmap()
  572. {
  573. return CreateAliasedBitmap(this.Bounds);
  574. }
  575. /// <summary>
  576. /// Helper function. Same as calling CreateAliasedBounds(bounds, true).
  577. /// </summary>
  578. /// <returns>A GDI+ Bitmap that aliases the entire Surface.</returns>
  579. public Bitmap CreateAliasedBitmap(Rectangle bounds)
  580. {
  581. return CreateAliasedBitmap(bounds, true);
  582. }
  583. /// <summary>
  584. /// Creates a GDI+ Bitmap object that aliases the same memory that this Surface does.
  585. /// Then you can use GDI+ to draw on to this surface.
  586. /// Note: Since the Bitmap does not hold a reference to this Surface object, nor to
  587. /// the MemoryBlock that it contains, you must hold a reference to the Surface object
  588. /// for as long as you wish to use the aliased Bitmap. Otherwise the memory may be
  589. /// freed and the Bitmap will look corrupt or cause other errors. You may use the
  590. /// RenderArgs class to help manage this lifetime instead.
  591. /// </summary>
  592. /// <param name="bounds">The rectangle of interest within this Surface that you wish to alias.</param>
  593. /// <param name="alpha">If true, the returned bitmap will use PixelFormat.Format32bppArgb.
  594. /// If false, the returned bitmap will use PixelFormat.Format32bppRgb.</param>
  595. /// <returns>A GDI+ Bitmap that aliases the requested portion of the Surface.</returns>
  596. /// <exception cref="ArgumentOutOfRangeException"><b>bounds</b> was not entirely within the boundaries of the Surface</exception>
  597. /// <exception cref="ObjectDisposedException">This Surface instance is already disposed.</exception>
  598. public Bitmap CreateAliasedBitmap(Rectangle bounds, bool alpha = true)
  599. {
  600. if (disposed)
  601. {
  602. throw new ObjectDisposedException("Surface");
  603. }
  604. if (bounds.IsEmpty)
  605. {
  606. throw new ArgumentOutOfRangeException();
  607. }
  608. Rectangle clipped = Rectangle.Intersect(this.Bounds, bounds);
  609. if (clipped != bounds)
  610. {
  611. throw new ArgumentOutOfRangeException();
  612. }
  613. unsafe
  614. {
  615. return new Bitmap(bounds.Width, bounds.Height, stride, alpha ? PixelFormat.Format32bppArgb : this.PixelFormat,
  616. new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y))));
  617. }
  618. }
  619. /// <summary>
  620. /// Creates a new Surface and copies the pixels from a Bitmap to it.
  621. /// </summary>
  622. /// <param name="bitmap">The Bitmap to duplicate.</param>
  623. /// <returns>A new Surface that is the same size as the given Bitmap and that has the same pixel values.</returns>
  624. public static Surface CopyFromBitmap(Bitmap bitmap)
  625. {
  626. Surface surface = new Surface(bitmap.Width, bitmap.Height);
  627. BitmapData bd = bitmap.LockBits(surface.Bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
  628. unsafe
  629. {
  630. for (int y = 0; y < bd.Height; ++y)
  631. {
  632. Memory.Copy((void*)surface.GetRowAddress(y),
  633. (byte*)bd.Scan0.ToPointer() + (y * bd.Stride), (ulong)bd.Width * ColorBgra.SizeOf);
  634. }
  635. }
  636. bitmap.UnlockBits(bd);
  637. return surface;
  638. }
  639. /// <summary>
  640. /// Copies the contents of the given surface to the upper left corner of this surface.
  641. /// </summary>
  642. /// <param name="source">The surface to copy pixels from.</param>
  643. /// <remarks>
  644. /// The source surface does not need to have the same dimensions as this surface. Clipping
  645. /// will be handled automatically. No resizing will be done.
  646. /// </remarks>
  647. public void CopySurface(Surface source)
  648. {
  649. if (disposed)
  650. {
  651. throw new ObjectDisposedException("Surface");
  652. }
  653. if (this.stride == source.stride &&
  654. (this.width * ColorBgra.SizeOf) == this.stride &&
  655. this.width == source.width &&
  656. this.height == source.height)
  657. {
  658. unsafe
  659. {
  660. Memory.Copy(this.scan0.VoidStar,
  661. source.scan0.VoidStar,
  662. ((ulong)(height - 1) * (ulong)stride) + ((ulong)width * (ulong)ColorBgra.SizeOf));
  663. }
  664. }
  665. else
  666. {
  667. int copyWidth = Math.Min(width, source.width);
  668. int copyHeight = Math.Min(height, source.height);
  669. unsafe
  670. {
  671. for (int y = 0; y < copyHeight; ++y)
  672. {
  673. Memory.Copy(GetRowAddressUnchecked(y), source.GetRowAddressUnchecked(y), (ulong)copyWidth * (ulong)ColorBgra.SizeOf);
  674. }
  675. }
  676. }
  677. }
  678. object ICloneable.Clone()
  679. {
  680. return Clone();
  681. }
  682. /// <summary>
  683. /// Creates a new surface with the same dimensions and pixel values as this one.
  684. /// </summary>
  685. /// <returns>A new surface that is a clone of the current one.</returns>
  686. public Surface Clone()
  687. {
  688. if (disposed)
  689. {
  690. throw new ObjectDisposedException("Surface");
  691. }
  692. Surface ret = new Surface(this.Size);
  693. ret.CopySurface(this);
  694. return ret;
  695. }
  696. /// <summary>
  697. /// Clears the surface to all-white (BGRA = [255,255,255,255]).
  698. /// </summary>
  699. public void Clear()
  700. {
  701. Clear(ColorBgra.FromBgra(255, 255, 255, 255));
  702. }
  703. /// <summary>
  704. /// Clears the surface to the given color value.
  705. /// </summary>
  706. /// <param name="color">The color value to fill the surface with.</param>
  707. public void Clear(ColorBgra color)
  708. {
  709. new UnaryPixelOps.Constant(color).Apply(this, this.Bounds);
  710. }
  711. /// <summary>
  712. /// Fits the source surface to this surface using super sampling. If the source surface is less wide
  713. /// or less tall than this surface (i.e. magnification), bicubic resampling is used instead. If either
  714. /// the source or destination has a dimension that is only 1 pixel, nearest neighbor is used.
  715. /// </summary>
  716. /// <param name="source">The surface to read pixels from.</param>
  717. /// <param name="dstRoi">The rectangle to clip rendering to.</param>
  718. /// <remarks>This method was implemented with correctness, not performance, in mind.</remarks>
  719. public void SuperSamplingFitSurface(Surface source, Rectangle dstRoi)
  720. {
  721. if (source.Width == Width && source.Height == Height)
  722. {
  723. CopySurface(source);
  724. }
  725. else if (source.Width <= Width || source.Height <= Height)
  726. {
  727. if (source.width < 2 || source.height < 2 || this.width < 2 || this.height < 2)
  728. {
  729. this.NearestNeighborFitSurface(source, dstRoi);
  730. }
  731. else
  732. {
  733. this.BicubicFitSurface(source, dstRoi);
  734. }
  735. }
  736. else unsafe
  737. {
  738. Rectangle dstRoi2 = Rectangle.Intersect(dstRoi, this.Bounds);
  739. for (int dstY = dstRoi2.Top; dstY < dstRoi2.Bottom; ++dstY)
  740. {
  741. double srcTop = (double)(dstY * source.height) / (double)height;
  742. double srcTopFloor = Math.Floor(srcTop);
  743. double srcTopWeight = 1 - (srcTop - srcTopFloor);
  744. int srcTopInt = (int)srcTopFloor;
  745. double srcBottom = (double)((dstY + 1) * source.height) / (double)height;
  746. double srcBottomFloor = Math.Floor(srcBottom - 0.00001);
  747. double srcBottomWeight = srcBottom - srcBottomFloor;
  748. int srcBottomInt = (int)srcBottomFloor;
  749. ColorBgra* dstPtr = this.GetPointAddressUnchecked(dstRoi2.Left, dstY);
  750. for (int dstX = dstRoi2.Left; dstX < dstRoi2.Right; ++dstX)
  751. {
  752. double srcLeft = (double)(dstX * source.width) / (double)width;
  753. double srcLeftFloor = Math.Floor(srcLeft);
  754. double srcLeftWeight = 1 - (srcLeft - srcLeftFloor);
  755. int srcLeftInt = (int)srcLeftFloor;
  756. double srcRight = (double)((dstX + 1) * source.width) / (double)width;
  757. double srcRightFloor = Math.Floor(srcRight - 0.00001);
  758. double srcRightWeight = srcRight - srcRightFloor;
  759. int srcRightInt = (int)srcRightFloor;
  760. double blueSum = 0;
  761. double greenSum = 0;
  762. double redSum = 0;
  763. double alphaSum = 0;
  764. // left fractional edge
  765. ColorBgra* srcLeftPtr = source.GetPointAddressUnchecked(srcLeftInt, srcTopInt + 1);
  766. for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY)
  767. {
  768. double a = srcLeftPtr->A;
  769. blueSum += srcLeftPtr->B * srcLeftWeight * a;
  770. greenSum += srcLeftPtr->G * srcLeftWeight * a;
  771. redSum += srcLeftPtr->R * srcLeftWeight * a;
  772. alphaSum += srcLeftPtr->A * srcLeftWeight;
  773. srcLeftPtr = (ColorBgra*)((byte*)srcLeftPtr + source.stride);
  774. }
  775. // right fractional edge
  776. ColorBgra* srcRightPtr = source.GetPointAddressUnchecked(srcRightInt, srcTopInt + 1);
  777. for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY)
  778. {
  779. double a = srcRightPtr->A;
  780. blueSum += srcRightPtr->B * srcRightWeight * a;
  781. greenSum += srcRightPtr->G * srcRightWeight * a;
  782. redSum += srcRightPtr->R * srcRightWeight * a;
  783. alphaSum += srcRightPtr->A * srcRightWeight;
  784. srcRightPtr = (ColorBgra*)((byte*)srcRightPtr + source.stride);
  785. }
  786. // top fractional edge
  787. ColorBgra* srcTopPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcTopInt);
  788. for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX)
  789. {
  790. double a = srcTopPtr->A;
  791. blueSum += srcTopPtr->B * srcTopWeight * a;
  792. greenSum += srcTopPtr->G * srcTopWeight * a;
  793. redSum += srcTopPtr->R * srcTopWeight * a;
  794. alphaSum += srcTopPtr->A * srcTopWeight;
  795. ++srcTopPtr;
  796. }
  797. // bottom fractional edge
  798. ColorBgra* srcBottomPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcBottomInt);
  799. for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX)
  800. {
  801. double a = srcBottomPtr->A;
  802. blueSum += srcBottomPtr->B * srcBottomWeight * a;
  803. greenSum += srcBottomPtr->G * srcBottomWeight * a;
  804. redSum += srcBottomPtr->R * srcBottomWeight * a;
  805. alphaSum += srcBottomPtr->A * srcBottomWeight;
  806. ++srcBottomPtr;
  807. }
  808. // center area
  809. for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY)
  810. {
  811. ColorBgra* srcPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcY);
  812. for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX)
  813. {
  814. double a = srcPtr->A;
  815. blueSum += (double)srcPtr->B * a;
  816. greenSum += (double)srcPtr->G * a;
  817. redSum += (double)srcPtr->R * a;
  818. alphaSum += (double)srcPtr->A;
  819. ++srcPtr;
  820. }
  821. }
  822. // four corner pixels
  823. ColorBgra srcTL = source.GetPoint(srcLeftInt, srcTopInt);
  824. double srcTLA = srcTL.A;
  825. blueSum += srcTL.B * (srcTopWeight * srcLeftWeight) * srcTLA;
  826. greenSum += srcTL.G * (srcTopWeight * srcLeftWeight) * srcTLA;
  827. redSum += srcTL.R * (srcTopWeight * srcLeftWeight) * srcTLA;
  828. alphaSum += srcTL.A * (srcTopWeight * srcLeftWeight);
  829. ColorBgra srcTR = source.GetPoint(srcRightInt, srcTopInt);
  830. double srcTRA = srcTR.A;
  831. blueSum += srcTR.B * (srcTopWeight * srcRightWeight) * srcTRA;
  832. greenSum += srcTR.G * (srcTopWeight * srcRightWeight) * srcTRA;
  833. redSum += srcTR.R * (srcTopWeight * srcRightWeight) * srcTRA;
  834. alphaSum += srcTR.A * (srcTopWeight * srcRightWeight);
  835. ColorBgra srcBL = source.GetPoint(srcLeftInt, srcBottomInt);
  836. double srcBLA = srcBL.A;
  837. blueSum += srcBL.B * (srcBottomWeight * srcLeftWeight) * srcBLA;
  838. greenSum += srcBL.G * (srcBottomWeight * srcLeftWeight) * srcBLA;
  839. redSum += srcBL.R * (srcBottomWeight * srcLeftWeight) * srcBLA;
  840. alphaSum += srcBL.A * (srcBottomWeight * srcLeftWeight);
  841. ColorBgra srcBR = source.GetPoint(srcRightInt, srcBottomInt);
  842. double srcBRA = srcBR.A;
  843. blueSum += srcBR.B * (srcBottomWeight * srcRightWeight) * srcBRA;
  844. greenSum += srcBR.G * (srcBottomWeight * srcRightWeight) * srcBRA;
  845. redSum += srcBR.R * (srcBottomWeight * srcRightWeight) * srcBRA;
  846. alphaSum += srcBR.A * (srcBottomWeight * srcRightWeight);
  847. double area = (srcRight - srcLeft) * (srcBottom - srcTop);
  848. double alpha = alphaSum / area;
  849. double blue;
  850. double green;
  851. double red;
  852. if (alpha == 0)
  853. {
  854. blue = 0;
  855. green = 0;
  856. red = 0;
  857. }
  858. else
  859. {
  860. blue = blueSum / alphaSum;
  861. green = greenSum / alphaSum;
  862. red = redSum / alphaSum;
  863. }
  864. // add 0.5 so that rounding goes in the direction we want it to
  865. blue += 0.5;
  866. green += 0.5;
  867. red += 0.5;
  868. alpha += 0.5;
  869. dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24);
  870. ++dstPtr;
  871. }
  872. }
  873. }
  874. }
  875. /// <summary>
  876. /// Fits the source surface to this surface using nearest neighbor resampling.
  877. /// </summary>
  878. /// <param name="source">The surface to read pixels from.</param>
  879. /// <param name="dstRoi">The rectangle to clip rendering to.</param>
  880. public void NearestNeighborFitSurface(Surface source, Rectangle dstRoi)
  881. {
  882. Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds);
  883. unsafe
  884. {
  885. for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY)
  886. {
  887. int srcY = (dstY * source.height) / height;
  888. ColorBgra* srcRow = source.GetRowAddressUnchecked(srcY);
  889. ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY);
  890. for (int dstX = roi.Left; dstX < roi.Right; ++dstX)
  891. {
  892. int srcX = (dstX * source.width) / width;
  893. *dstPtr = *(srcRow + srcX);
  894. ++dstPtr;
  895. }
  896. }
  897. }
  898. }
  899. private double CubeClamped(double x)
  900. {
  901. if (x >= 0)
  902. {
  903. return x * x * x;
  904. }
  905. else
  906. {
  907. return 0;
  908. }
  909. }
  910. /// <summary>
  911. /// Implements R() as defined at http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/
  912. /// </summary>
  913. private double R(double x)
  914. {
  915. return (CubeClamped(x + 2) - (4 * CubeClamped(x + 1)) + (6 * CubeClamped(x)) - (4 * CubeClamped(x - 1))) / 6;
  916. }
  917. /// <summary>
  918. /// Fits the source surface to this surface using bicubic interpolation.
  919. /// </summary>
  920. /// <param name="source">The Surface to read pixels from.</param>
  921. /// <param name="dstRoi">The rectangle to clip rendering to.</param>
  922. /// <remarks>
  923. /// This method was implemented with correctness, not performance, in mind.
  924. /// Based on: "Bicubic Interpolation for Image Scaling" by Paul Bourke,
  925. /// http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/
  926. /// </remarks>
  927. public void BicubicFitSurface(Surface source, Rectangle dstRoi)
  928. {
  929. float leftF = (1 * (float)(width - 1)) / (float)(source.width - 1);
  930. float topF = (1 * (height - 1)) / (float)(source.height - 1);
  931. float rightF = ((float)(source.width - 3) * (float)(width - 1)) / (float)(source.width - 1);
  932. float bottomF = ((float)(source.Height - 3) * (float)(height - 1)) / (float)(source.height - 1);
  933. int left = (int)Math.Ceiling((double)leftF);
  934. int top = (int)Math.Ceiling((double)topF);
  935. int right = (int)Math.Floor((double)rightF);
  936. int bottom = (int)Math.Floor((double)bottomF);
  937. Rectangle[] rois = new Rectangle[] {
  938. Rectangle.FromLTRB(left, top, right, bottom),
  939. new Rectangle(0, 0, width, top),
  940. new Rectangle(0, top, left, height - top),
  941. new Rectangle(right, top, width - right, height - top),
  942. new Rectangle(left, bottom, right - left, height - bottom)
  943. };
  944. for (int i = 0; i < rois.Length; ++i)
  945. {
  946. rois[i].Intersect(dstRoi);
  947. if (rois[i].Width > 0 && rois[i].Height > 0)
  948. {
  949. if (i == 0)
  950. {
  951. BicubicFitSurfaceUnchecked(source, rois[i]);
  952. }
  953. else
  954. {
  955. BicubicFitSurfaceChecked(source, rois[i]);
  956. }
  957. }
  958. }
  959. }
  960. /// <summary>
  961. /// Implements bicubic filtering with bounds checking at every pixel.
  962. /// </summary>
  963. private void BicubicFitSurfaceChecked(Surface source, Rectangle dstRoi)
  964. {
  965. if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2)
  966. {
  967. SuperSamplingFitSurface(source, dstRoi);
  968. }
  969. else
  970. {
  971. unsafe
  972. {
  973. Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds);
  974. Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1));
  975. IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double));
  976. double* rColCache = (double*)rColCacheIP.ToPointer();
  977. // Precompute and then cache the value of R() for each column
  978. for (int dstX = roi.Left; dstX < roi.Right; ++dstX)
  979. {
  980. double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1);
  981. double srcColumnFloor = Math.Floor(srcColumn);
  982. double srcColumnFrac = srcColumn - srcColumnFloor;
  983. int srcColumnInt = (int)srcColumn;
  984. for (int m = -1; m <= 2; ++m)
  985. {
  986. int index = (m + 1) + ((dstX - roi.Left) * 4);
  987. double x = m - srcColumnFrac;
  988. rColCache[index] = R(x);
  989. }
  990. }
  991. // Set this up so we can cache the R()'s for every row
  992. double* rRowCache = stackalloc double[4];
  993. for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY)
  994. {
  995. double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1);
  996. double srcRowFloor = (double)Math.Floor(srcRow);
  997. double srcRowFrac = srcRow - srcRowFloor;
  998. int srcRowInt = (int)srcRow;
  999. ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY);
  1000. // Compute the R() values for this row
  1001. for (int n = -1; n <= 2; ++n)
  1002. {
  1003. double x = srcRowFrac - n;
  1004. rRowCache[n + 1] = R(x);
  1005. }
  1006. // See Perf Note below
  1007. //int nFirst = Math.Max(-srcRowInt, -1);
  1008. //int nLast = Math.Min(source.height - srcRowInt - 1, 2);
  1009. for (int dstX = roi.Left; dstX < roi.Right; dstX++)
  1010. {
  1011. double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1);
  1012. double srcColumnFloor = Math.Floor(srcColumn);
  1013. double srcColumnFrac = srcColumn - srcColumnFloor;
  1014. int srcColumnInt = (int)srcColumn;
  1015. double blueSum = 0;
  1016. double greenSum = 0;
  1017. double redSum = 0;
  1018. double alphaSum = 0;
  1019. double totalWeight = 0;
  1020. // See Perf Note below
  1021. //int mFirst = Math.Max(-srcColumnInt, -1);
  1022. //int mLast = Math.Min(source.width - srcColumnInt - 1, 2);
  1023. ColorBgra* srcPtr = source.GetPointAddressUnchecked(srcColumnInt - 1, srcRowInt - 1);
  1024. for (int n = -1; n <= 2; ++n)
  1025. {
  1026. int srcY = srcRowInt + n;
  1027. for (int m = -1; m <= 2; ++m)
  1028. {
  1029. // Perf Note: It actually benchmarks faster on my system to do
  1030. // a bounds check for every (m,n) than it is to limit the loop
  1031. // to nFirst-Last and mFirst-mLast.
  1032. // I'm leaving the code above, albeit commented out, so that
  1033. // benchmarking between these two can still be performed.
  1034. if (source.IsVisible(srcColumnInt + m, srcY))
  1035. {
  1036. double w0 = rColCache[(m + 1) + (4 * (dstX - roi.Left))];
  1037. double w1 = rRowCache[n + 1];
  1038. double w = w0 * w1;
  1039. blueSum += srcPtr->B * w * srcPtr->A;
  1040. greenSum += srcPtr->G * w * srcPtr->A;
  1041. redSum += srcPtr->R * w * srcPtr->A;
  1042. alphaSum += srcPtr->A * w;
  1043. totalWeight += w;
  1044. }
  1045. ++srcPtr;
  1046. }
  1047. srcPtr = (ColorBgra*)((byte*)(srcPtr - 4) + source.stride);
  1048. }
  1049. double alpha = alphaSum / totalWeight;
  1050. double blue;
  1051. double green;
  1052. double red;
  1053. if (alpha == 0)
  1054. {
  1055. blue = 0;
  1056. green = 0;
  1057. red = 0;
  1058. }
  1059. else
  1060. {
  1061. blue = blueSum / alphaSum;
  1062. green = greenSum / alphaSum;
  1063. red = redSum / alphaSum;
  1064. // add 0.5 to ensure truncation to uint results in rounding
  1065. alpha += 0.5;
  1066. blue += 0.5;
  1067. green += 0.5;
  1068. red += 0.5;
  1069. }
  1070. dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24);
  1071. ++dstPtr;
  1072. } // for (dstX...
  1073. } // for (dstY...
  1074. Memory.Free(rColCacheIP);
  1075. } // unsafe
  1076. }
  1077. }
  1078. /// <summary>
  1079. /// Implements bicubic filtering with NO bounds checking at any pixel.
  1080. /// </summary>
  1081. public void BicubicFitSurfaceUnchecked(Surface source, Rectangle dstRoi)
  1082. {
  1083. if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2)
  1084. {
  1085. SuperSamplingFitSurface(source, dstRoi);
  1086. }
  1087. else
  1088. {
  1089. unsafe
  1090. {
  1091. Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds);
  1092. Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1));
  1093. IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double));
  1094. double* rColCache = (double*)rColCacheIP.ToPointer();
  1095. // Precompute and then cache the value of R() for each column
  1096. for (int dstX = roi.Left; dstX < roi.Right; ++dstX)
  1097. {
  1098. double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1);
  1099. double srcColumnFloor = Math.Floor(srcColumn);
  1100. double srcColumnFrac = srcColumn - srcColumnFloor;
  1101. int srcColumnInt = (int)srcColumn;
  1102. for (int m = -1; m <= 2; ++m)
  1103. {
  1104. int index = (m + 1) + ((dstX - roi.Left) * 4);
  1105. double x = m - srcColumnFrac;
  1106. rColCache[index] = R(x);
  1107. }
  1108. }
  1109. // Set this up so we can cache the R()'s for every row
  1110. double* rRowCache = stackalloc double[4];
  1111. for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY)
  1112. {
  1113. double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1);
  1114. double srcRowFloor = Math.Floor(srcRow);
  1115. double srcRowFrac = srcRow - srcRowFloor;
  1116. int srcRowInt = (int)srcRow;
  1117. ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY);
  1118. // Compute the R() values for this row
  1119. for (int n = -1; n <= 2; ++n)
  1120. {
  1121. double x = srcRowFrac - n;
  1122. rRowCache[n + 1] = R(x);
  1123. }
  1124. rColCache = (double*)rColCacheIP.ToPointer();
  1125. ColorBgra* srcRowPtr = source.GetRowAddressUnchecked(srcRowInt - 1);
  1126. for (int dstX = roi.Left; dstX < roi.Right; dstX++)
  1127. {
  1128. double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1);
  1129. double srcColumnFloor = Math.Floor(srcColumn);
  1130. double srcColumnFrac = srcColumn - srcColumnFloor;
  1131. int srcColumnInt = (int)srcColumn;
  1132. double blueSum = 0;
  1133. double greenSum = 0;
  1134. double redSum = 0;
  1135. double alphaSum = 0;
  1136. double totalWeight = 0;
  1137. ColorBgra* srcPtr = srcRowPtr + srcColumnInt - 1;
  1138. for (int n = 0; n <= 3; ++n)
  1139. {
  1140. double w0 = rColCache[0] * rRowCache[n];
  1141. double w1 = rColCache[1] * rRowCache[n];
  1142. double w2 = rColCache[2] * rRowCache[n];
  1143. double w3 = rColCache[3] * rRowCache[n];
  1144. double a0 = srcPtr[0].A;
  1145. double a1 = srcPtr[1].A;
  1146. double a2 = srcPtr[2].A;
  1147. double a3 = srcPtr[3].A;
  1148. alphaSum += (a0 * w0) + (a1 * w1) + (a2 * w2) + (a3 * w3);
  1149. totalWeight += w0 + w1 + w2 + w3;
  1150. blueSum += (a0 * srcPtr[0].B * w0) + (a1 * srcPtr[1].B * w1) + (a2 * srcPtr[2].B * w2) + (a3 * srcPtr[3].B * w3);
  1151. greenSum += (a0 * srcPtr[0].G * w0) + (a1 * srcPtr[1].G * w1) + (a2 * srcPtr[2].G * w2) + (a3 * srcPtr[3].G * w3);
  1152. redSum += (a0 * srcPtr[0].R * w0) + (a1 * srcPtr[1].R * w1) + (a2 * srcPtr[2].R * w2) + (a3 * srcPtr[3].R * w3);
  1153. srcPtr = (ColorBgra*)((byte*)srcPtr + source.stride);
  1154. }
  1155. double alpha = alphaSum / totalWeight;
  1156. double blue;
  1157. double green;
  1158. double red;
  1159. if (alpha == 0)
  1160. {
  1161. blue = 0;
  1162. green = 0;
  1163. red = 0;
  1164. }
  1165. else
  1166. {
  1167. blue = blueSum / alphaSum;
  1168. green = greenSum / alphaSum;
  1169. red = redSum / alphaSum;
  1170. // add 0.5 to ensure truncation to uint results in rounding
  1171. alpha += 0.5;
  1172. blue += 0.5;
  1173. green += 0.5;
  1174. red += 0.5;
  1175. }
  1176. dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24);
  1177. ++dstPtr;
  1178. rColCache += 4;
  1179. } // for (dstX...
  1180. } // for (dstY...
  1181. Memory.Free(rColCacheIP);
  1182. } // unsafe
  1183. }
  1184. }
  1185. /// <summary>
  1186. /// Releases all resources held by this Surface object.
  1187. /// </summary>
  1188. public void Dispose()
  1189. {
  1190. Dispose(true);
  1191. GC.SuppressFinalize(this);
  1192. }
  1193. private void Dispose(bool disposing)
  1194. {
  1195. if (!disposed)
  1196. {
  1197. disposed = true;
  1198. if (disposing)
  1199. {
  1200. scan0.Dispose();
  1201. scan0 = null;
  1202. }
  1203. }
  1204. }
  1205. ///<summary>
  1206. /// 生成缩略图
  1207. /// </summary>
  1208. /// <param name="originalImage">源图片</param>
  1209. /// <param name="width">缩略图宽度</param>
  1210. /// <param name="height">缩略图高度</param>
  1211. /// <param name="mode">生成缩略图的方式</param>
  1212. public static Bitmap MakeThumbnail(Image originalImage, int width, int height, string mode)
  1213. {
  1214. int towidth = width;
  1215. int toheight = height;
  1216. int x = 0;
  1217. int y = 0;
  1218. int ow = originalImage.Width;
  1219. int oh = originalImage.Height;
  1220. switch (mode)
  1221. {
  1222. case "HW"://指定高宽缩放(可能变形)
  1223. break;
  1224. case "W"://指定宽,高按比例
  1225. toheight = originalImage.Height * width / originalImage.Width;
  1226. break;
  1227. case "H"://指定高,宽按比例
  1228. towidth = originalImage.Width * height / originalImage.Height;
  1229. break;
  1230. case "Cut"://指定高宽裁减(不变形)
  1231. if ((double)originalImage.Width / (double)originalImage.Height > (double)towidth / (double)toheight)
  1232. {
  1233. oh = originalImage.Height;
  1234. ow = originalImage.Height * towidth / toheight;
  1235. y = 0;
  1236. x = (originalImage.Width - ow) / 2;
  1237. }
  1238. else
  1239. {
  1240. ow = originalImage.Width;
  1241. oh = originalImage.Width * height / towidth;
  1242. x = 0;
  1243. y = (originalImage.Height - oh) / 2;
  1244. }
  1245. break;
  1246. default:
  1247. break;
  1248. }
  1249. //新建一个bmp图片
  1250. Bitmap bitmap = new System.Drawing.Bitmap(towidth, toheight);
  1251. //新建一个画板
  1252. Graphics g = System.Drawing.Graphics.FromImage(bitmap);
  1253. //设置高质量插值法
  1254. g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
  1255. //设置高质量,低速度呈现平滑程度
  1256. g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
  1257. //清空画布并以透明背景色填充
  1258. g.Clear(Color.Transparent);
  1259. //在指定位置并且按指定大小绘制原图片的指定部分
  1260. g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight),
  1261. new Rectangle(x, y, ow, oh),
  1262. GraphicsUnit.Pixel);
  1263. return bitmap;
  1264. }
  1265. }
  1266. }