PageRenderTime 58ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/WBX_1.0_WinMD/Source/WriteableBitmapEx/WriteableBitmapTransformationExtensions.cs

#
C# | 619 lines | 410 code | 80 blank | 129 comment | 65 complexity | 6a12eab539101a514d04a6ef3db64f03 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. #region Header
  2. //
  3. // Project: WriteableBitmapEx - WriteableBitmap extensions
  4. // Description: Collection of transformation extension methods for the WriteableBitmap class.
  5. //
  6. // Changed by: $Author: unknown $
  7. // Changed on: $Date: 2012-05-03 23:12:09 +0200 (Do, 03 Mai 2012) $
  8. // Changed in: $Revision: 90031 $
  9. // Project: $URL: https://writeablebitmapex.svn.codeplex.com/svn/branches/WBX_1.0_BitmapContext/Source/WriteableBitmapEx/WriteableBitmapTransformationExtensions.cs $
  10. // Id: $Id: WriteableBitmapTransformationExtensions.cs 90031 2012-05-03 21:12:09Z unknown $
  11. //
  12. //
  13. // Copyright Š 2009-2012 Rene Schulte and WriteableBitmapEx Contributors
  14. //
  15. // This Software is weak copyleft open source. Please read the License.txt for details.
  16. //
  17. #endregion
  18. using System;
  19. #if NETFX_CORE
  20. using Windows.Foundation;
  21. namespace Windows.UI.Xaml.Media.Imaging
  22. #else
  23. namespace System.Windows.Media.Imaging
  24. #endif
  25. {
  26. #region Enums
  27. /// <summary>
  28. /// The interpolation method.
  29. /// </summary>
  30. public enum Interpolation
  31. {
  32. /// <summary>
  33. /// The nearest neighbor algorithm simply selects the color of the nearest pixel.
  34. /// </summary>
  35. NearestNeighbor = 0,
  36. /// <summary>
  37. /// Linear interpolation in 2D using the average of 3 neighboring pixels.
  38. /// </summary>
  39. Bilinear,
  40. }
  41. /// <summary>
  42. /// The mode for flipping.
  43. /// </summary>
  44. public enum FlipMode
  45. {
  46. /// <summary>
  47. /// Flips the image vertical (around the center of the y-axis).
  48. /// </summary>
  49. Vertical,
  50. /// <summary>
  51. /// Flips the image horizontal (around the center of the x-axis).
  52. /// </summary>
  53. Horizontal
  54. }
  55. #endregion
  56. /// <summary>
  57. /// Collection of transformation extension methods for the WriteableBitmap class.
  58. /// </summary>
  59. public
  60. #if WPF
  61. unsafe
  62. #endif
  63. static partial class WriteableBitmapExtensions
  64. {
  65. #region Methods
  66. #region Crop
  67. /// <summary>
  68. /// Creates a new cropped WriteableBitmap.
  69. /// </summary>
  70. /// <param name="bmp">The WriteableBitmap.</param>
  71. /// <param name="x">The x coordinate of the rectangle that defines the crop region.</param>
  72. /// <param name="y">The y coordinate of the rectangle that defines the crop region.</param>
  73. /// <param name="width">The width of the rectangle that defines the crop region.</param>
  74. /// <param name="height">The height of the rectangle that defines the crop region.</param>
  75. /// <returns>A new WriteableBitmap that is a cropped version of the input.</returns>
  76. public static WriteableBitmap Crop(this WriteableBitmap bmp, int x, int y, int width, int height)
  77. {
  78. using (var srcContext = bmp.GetBitmapContext())
  79. {
  80. var srcWidth = srcContext.Width;
  81. var srcHeight = srcContext.Height;
  82. // If the rectangle is completly out of the bitmap
  83. if (x > srcWidth || y > srcHeight)
  84. {
  85. return BitmapFactory.New(0, 0);
  86. }
  87. // Clamp to boundaries
  88. if (x < 0) x = 0;
  89. if (x + width > srcWidth) width = srcWidth - x;
  90. if (y < 0) y = 0;
  91. if (y + height > srcHeight) height = srcHeight - y;
  92. // Copy the pixels line by line using fast BlockCopy
  93. var result = BitmapFactory.New(width, height);
  94. using (var destContext = result.GetBitmapContext())
  95. {
  96. for (var line = 0; line < height; line++)
  97. {
  98. var srcOff = ((y + line) * srcWidth + x) * SizeOfArgb;
  99. var dstOff = line * width * SizeOfArgb;
  100. BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, width * SizeOfArgb);
  101. }
  102. return result;
  103. }
  104. }
  105. }
  106. #if !WINMD
  107. /// <summary>
  108. /// Creates a new cropped WriteableBitmap.
  109. /// </summary>
  110. /// <param name="bmp">The WriteableBitmap.</param>
  111. /// <param name="region">The rectangle that defines the crop region.</param>
  112. /// <returns>A new WriteableBitmap that is a cropped version of the input.</returns>
  113. public static WriteableBitmap Crop(this WriteableBitmap bmp, Rect region)
  114. {
  115. return bmp.Crop((int)region.X, (int)region.Y, (int)region.Width, (int)region.Height);
  116. }
  117. #endif
  118. #endregion
  119. #region Resize
  120. /// <summary>
  121. /// Creates a new resized WriteableBitmap.
  122. /// </summary>
  123. /// <param name="bmp">The WriteableBitmap.</param>
  124. /// <param name="width">The new desired width.</param>
  125. /// <param name="height">The new desired height.</param>
  126. /// <param name="interpolation">The interpolation method that should be used.</param>
  127. /// <returns>A new WriteableBitmap that is a resized version of the input.</returns>
  128. public static WriteableBitmap Resize(this WriteableBitmap bmp, int width, int height, Interpolation interpolation)
  129. {
  130. using (var srcContext = bmp.GetBitmapContext())
  131. {
  132. var pd = Resize(srcContext, srcContext.Width, srcContext.Height, width, height, interpolation);
  133. var result = BitmapFactory.New(width, height);
  134. BitmapContext.BlockCopy(pd, 0, srcContext, 0, SizeOfArgb * pd.Length);
  135. return result;
  136. }
  137. }
  138. /// <summary>
  139. /// Creates a new resized bitmap.
  140. /// </summary>
  141. /// <param name="srcContext">The source context.</param>
  142. /// <param name="widthSource">The width of the source pixels.</param>
  143. /// <param name="heightSource">The height of the source pixels.</param>
  144. /// <param name="width">The new desired width.</param>
  145. /// <param name="height">The new desired height.</param>
  146. /// <param name="interpolation">The interpolation method that should be used.</param>
  147. /// <returns>A new bitmap that is a resized version of the input.</returns>
  148. public static int[] Resize(BitmapContext srcContext, int widthSource, int heightSource, int width, int height, Interpolation interpolation)
  149. {
  150. var pixels = srcContext.Pixels;
  151. var pd = new int[width * height];
  152. var xs = (float)widthSource / width;
  153. var ys = (float)heightSource / height;
  154. float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf;
  155. int c, x0, x1, y0, y1;
  156. byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
  157. byte a, r, g, b;
  158. // Nearest Neighbor
  159. if (interpolation == Interpolation.NearestNeighbor)
  160. {
  161. var srcIdx = 0;
  162. for (var y = 0; y < height; y++)
  163. {
  164. for (var x = 0; x < width; x++)
  165. {
  166. sx = x * xs;
  167. sy = y * ys;
  168. x0 = (int)sx;
  169. y0 = (int)sy;
  170. pd[srcIdx++] = pixels[y0 * widthSource + x0];
  171. }
  172. }
  173. }
  174. // Bilinear
  175. else if (interpolation == Interpolation.Bilinear)
  176. {
  177. var srcIdx = 0;
  178. for (var y = 0; y < height; y++)
  179. {
  180. for (var x = 0; x < width; x++)
  181. {
  182. sx = x * xs;
  183. sy = y * ys;
  184. x0 = (int)sx;
  185. y0 = (int)sy;
  186. // Calculate coordinates of the 4 interpolation points
  187. fracx = sx - x0;
  188. fracy = sy - y0;
  189. ifracx = 1f - fracx;
  190. ifracy = 1f - fracy;
  191. x1 = x0 + 1;
  192. if (x1 >= widthSource)
  193. {
  194. x1 = x0;
  195. }
  196. y1 = y0 + 1;
  197. if (y1 >= heightSource)
  198. {
  199. y1 = y0;
  200. }
  201. // Read source color
  202. c = pixels[y0 * widthSource + x0];
  203. c1a = (byte)(c >> 24);
  204. c1r = (byte)(c >> 16);
  205. c1g = (byte)(c >> 8);
  206. c1b = (byte)(c);
  207. c = pixels[y0 * widthSource + x1];
  208. c2a = (byte)(c >> 24);
  209. c2r = (byte)(c >> 16);
  210. c2g = (byte)(c >> 8);
  211. c2b = (byte)(c);
  212. c = pixels[y1 * widthSource + x0];
  213. c3a = (byte)(c >> 24);
  214. c3r = (byte)(c >> 16);
  215. c3g = (byte)(c >> 8);
  216. c3b = (byte)(c);
  217. c = pixels[y1 * widthSource + x1];
  218. c4a = (byte)(c >> 24);
  219. c4r = (byte)(c >> 16);
  220. c4g = (byte)(c >> 8);
  221. c4b = (byte)(c);
  222. // Calculate colors
  223. // Alpha
  224. l0 = ifracx * c1a + fracx * c2a;
  225. l1 = ifracx * c3a + fracx * c4a;
  226. a = (byte)(ifracy * l0 + fracy * l1);
  227. // Red
  228. l0 = ifracx * c1r * c1a + fracx * c2r * c2a;
  229. l1 = ifracx * c3r * c3a + fracx * c4r * c4a;
  230. rf = ifracy * l0 + fracy * l1;
  231. // Green
  232. l0 = ifracx * c1g * c1a + fracx * c2g * c2a;
  233. l1 = ifracx * c3g * c3a + fracx * c4g * c4a;
  234. gf = ifracy * l0 + fracy * l1;
  235. // Blue
  236. l0 = ifracx * c1b * c1a + fracx * c2b * c2a;
  237. l1 = ifracx * c3b * c3a + fracx * c4b * c4a;
  238. bf = ifracy * l0 + fracy * l1;
  239. // Divide by alpha
  240. if (a > 0)
  241. {
  242. rf = rf / a;
  243. gf = gf / a;
  244. bf = bf / a;
  245. }
  246. // Cast to byte
  247. r = (byte)rf;
  248. g = (byte)gf;
  249. b = (byte)bf;
  250. // Write destination
  251. pd[srcIdx++] = (a << 24) | (r << 16) | (g << 8) | b;
  252. }
  253. }
  254. }
  255. return pd;
  256. }
  257. #endregion
  258. #region Rotate
  259. /// <summary>
  260. /// Rotates the bitmap in 90° steps clockwise and returns a new rotated WriteableBitmap.
  261. /// </summary>
  262. /// <param name="bmp">The WriteableBitmap.</param>
  263. /// <param name="angle">The angle in degress the bitmap should be rotated in 90° steps clockwise.</param>
  264. /// <returns>A new WriteableBitmap that is a rotated version of the input.</returns>
  265. public static WriteableBitmap Rotate(this WriteableBitmap bmp, int angle)
  266. {
  267. using (var context = bmp.GetBitmapContext())
  268. {
  269. // Use refs for faster access (really important!) speeds up a lot!
  270. var w = context.Width;
  271. var h = context.Height;
  272. var p = context.Pixels;
  273. var i = 0;
  274. WriteableBitmap result = null;
  275. angle %= 360;
  276. if (angle > 0 && angle <= 90)
  277. {
  278. result = BitmapFactory.New(h, w);
  279. using (var destContext = result.GetBitmapContext())
  280. {
  281. var rp = destContext.Pixels;
  282. for (var x = 0; x < w; x++)
  283. {
  284. for (var y = h - 1; y >= 0; y--)
  285. {
  286. var srcInd = y * w + x;
  287. rp[i] = p[srcInd];
  288. i++;
  289. }
  290. }
  291. }
  292. }
  293. else if (angle > 90 && angle <= 180)
  294. {
  295. result = BitmapFactory.New(w, h);
  296. using (var destContext = result.GetBitmapContext())
  297. {
  298. var rp = destContext.Pixels;
  299. for (var y = h - 1; y >= 0; y--)
  300. {
  301. for (var x = w - 1; x >= 0; x--)
  302. {
  303. var srcInd = y * w + x;
  304. rp[i] = p[srcInd];
  305. i++;
  306. }
  307. }
  308. }
  309. }
  310. else if (angle > 180 && angle <= 270)
  311. {
  312. result = BitmapFactory.New(h, w);
  313. using (var destContext = result.GetBitmapContext())
  314. {
  315. var rp = destContext.Pixels;
  316. for (var x = w - 1; x >= 0; x--)
  317. {
  318. for (var y = 0; y < h; y++)
  319. {
  320. var srcInd = y * w + x;
  321. rp[i] = p[srcInd];
  322. i++;
  323. }
  324. }
  325. }
  326. }
  327. else
  328. {
  329. result = bmp.Clone();
  330. }
  331. return result;
  332. }
  333. }
  334. /// <summary>
  335. /// Rotates the bitmap in any degree returns a new rotated WriteableBitmap.
  336. /// </summary>
  337. /// <param name="bmp">The WriteableBitmap.</param>
  338. /// <param name="angle">Arbitrary angle in 360 Degrees (positive = clockwise).</param>
  339. /// <param name="crop">if true: keep the size, false: adjust canvas to new size</param>
  340. /// <returns>A new WriteableBitmap that is a rotated version of the input.</returns>
  341. public static WriteableBitmap RotateFree(this WriteableBitmap bmp, double angle, bool crop = true)
  342. {
  343. // rotating clockwise, so it's negative relative to Cartesian quadrants
  344. double cnAngle = -1.0 * (Math.PI / 180) * angle;
  345. // general iterators
  346. int i, j;
  347. // calculated indices in Cartesian coordinates
  348. int x, y;
  349. double fDistance, fPolarAngle;
  350. // for use in neighbouring indices in Cartesian coordinates
  351. int iFloorX, iCeilingX, iFloorY, iCeilingY;
  352. // calculated indices in Cartesian coordinates with trailing decimals
  353. double fTrueX, fTrueY;
  354. // for interpolation
  355. double fDeltaX, fDeltaY;
  356. // pixel colours
  357. Color clrTopLeft, clrTopRight, clrBottomLeft, clrBottomRight;
  358. // interpolated "top" pixels
  359. double fTopRed, fTopGreen, fTopBlue, fTopAlpha;
  360. // interpolated "bottom" pixels
  361. double fBottomRed, fBottomGreen, fBottomBlue, fBottomAlpha;
  362. // final interpolated colour components
  363. int iRed, iGreen, iBlue, iAlpha;
  364. int iCentreX, iCentreY;
  365. int iDestCentreX, iDestCentreY;
  366. int iWidth, iHeight, newWidth, newHeight;
  367. using (var bmpContext = bmp.GetBitmapContext())
  368. {
  369. iWidth = bmpContext.Width;
  370. iHeight = bmpContext.Height;
  371. if (crop)
  372. {
  373. newWidth = iWidth;
  374. newHeight = iHeight;
  375. }
  376. else
  377. {
  378. var rad = angle / (180 / Math.PI);
  379. newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iHeight) + Math.Abs(Math.Cos(rad) * iWidth));
  380. newHeight = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iWidth) + Math.Abs(Math.Cos(rad) * iHeight));
  381. }
  382. iCentreX = iWidth / 2;
  383. iCentreY = iHeight / 2;
  384. iDestCentreX = newWidth / 2;
  385. iDestCentreY = newHeight / 2;
  386. var bmBilinearInterpolation = BitmapFactory.New(newWidth, newHeight);
  387. using (var bilinearContext = bmBilinearInterpolation.GetBitmapContext())
  388. {
  389. var newp = bilinearContext.Pixels;
  390. var oldp = bmpContext.Pixels;
  391. var oldw = bmpContext.Width;
  392. // assigning pixels of destination image from source image
  393. // with bilinear interpolation
  394. for (i = 0; i < newHeight; ++i)
  395. {
  396. for (j = 0; j < newWidth; ++j)
  397. {
  398. // convert raster to Cartesian
  399. x = j - iDestCentreX;
  400. y = iDestCentreY - i;
  401. // convert Cartesian to polar
  402. fDistance = Math.Sqrt(x * x + y * y);
  403. if (x == 0)
  404. {
  405. if (y == 0)
  406. {
  407. // centre of image, no rotation needed
  408. newp[i * newWidth + j] = oldp[iCentreY * oldw + iCentreX];
  409. continue;
  410. }
  411. if (y < 0)
  412. {
  413. fPolarAngle = 1.5 * Math.PI;
  414. }
  415. else
  416. {
  417. fPolarAngle = 0.5 * Math.PI;
  418. }
  419. }
  420. else
  421. {
  422. fPolarAngle = Math.Atan2(y, x);
  423. }
  424. // the crucial rotation part
  425. // "reverse" rotate, so minus instead of plus
  426. fPolarAngle -= cnAngle;
  427. // convert polar to Cartesian
  428. fTrueX = fDistance * Math.Cos(fPolarAngle);
  429. fTrueY = fDistance * Math.Sin(fPolarAngle);
  430. // convert Cartesian to raster
  431. fTrueX = fTrueX + iCentreX;
  432. fTrueY = iCentreY - fTrueY;
  433. iFloorX = (int)(Math.Floor(fTrueX));
  434. iFloorY = (int)(Math.Floor(fTrueY));
  435. iCeilingX = (int)(Math.Ceiling(fTrueX));
  436. iCeilingY = (int)(Math.Ceiling(fTrueY));
  437. // check bounds
  438. if (iFloorX < 0 || iCeilingX < 0 || iFloorX >= iWidth || iCeilingX >= iWidth || iFloorY < 0 ||
  439. iCeilingY < 0 || iFloorY >= iHeight || iCeilingY >= iHeight) continue;
  440. fDeltaX = fTrueX - iFloorX;
  441. fDeltaY = fTrueY - iFloorY;
  442. clrTopLeft = bmp.GetPixel(iFloorX, iFloorY);
  443. clrTopRight = bmp.GetPixel(iCeilingX, iFloorY);
  444. clrBottomLeft = bmp.GetPixel(iFloorX, iCeilingY);
  445. clrBottomRight = bmp.GetPixel(iCeilingX, iCeilingY);
  446. // linearly interpolate horizontally between top neighbours
  447. fTopRed = (1 - fDeltaX) * clrTopLeft.R + fDeltaX * clrTopRight.R;
  448. fTopGreen = (1 - fDeltaX) * clrTopLeft.G + fDeltaX * clrTopRight.G;
  449. fTopBlue = (1 - fDeltaX) * clrTopLeft.B + fDeltaX * clrTopRight.B;
  450. fTopAlpha = (1 - fDeltaX) * clrTopLeft.A + fDeltaX * clrTopRight.A;
  451. // linearly interpolate horizontally between bottom neighbours
  452. fBottomRed = (1 - fDeltaX) * clrBottomLeft.R + fDeltaX * clrBottomRight.R;
  453. fBottomGreen = (1 - fDeltaX) * clrBottomLeft.G + fDeltaX * clrBottomRight.G;
  454. fBottomBlue = (1 - fDeltaX) * clrBottomLeft.B + fDeltaX * clrBottomRight.B;
  455. fBottomAlpha = (1 - fDeltaX) * clrBottomLeft.A + fDeltaX * clrBottomRight.A;
  456. // linearly interpolate vertically between top and bottom interpolated results
  457. iRed = (int)(Math.Round((1 - fDeltaY) * fTopRed + fDeltaY * fBottomRed));
  458. iGreen = (int)(Math.Round((1 - fDeltaY) * fTopGreen + fDeltaY * fBottomGreen));
  459. iBlue = (int)(Math.Round((1 - fDeltaY) * fTopBlue + fDeltaY * fBottomBlue));
  460. iAlpha = (int)(Math.Round((1 - fDeltaY) * fTopAlpha + fDeltaY * fBottomAlpha));
  461. // make sure colour values are valid
  462. if (iRed < 0) iRed = 0;
  463. if (iRed > 255) iRed = 255;
  464. if (iGreen < 0) iGreen = 0;
  465. if (iGreen > 255) iGreen = 255;
  466. if (iBlue < 0) iBlue = 0;
  467. if (iBlue > 255) iBlue = 255;
  468. if (iAlpha < 0) iAlpha = 0;
  469. if (iAlpha > 255) iAlpha = 255;
  470. var a = iAlpha + 1;
  471. newp[i * newWidth + j] = (iAlpha << 24)
  472. | ((byte)((iRed * a) >> 8) << 16)
  473. | ((byte)((iGreen * a) >> 8) << 8)
  474. | ((byte)((iBlue * a) >> 8));
  475. }
  476. }
  477. return bmBilinearInterpolation;
  478. }
  479. }
  480. }
  481. #endregion
  482. #region Flip
  483. /// <summary>
  484. /// Flips (reflects the image) eiter vertical or horizontal.
  485. /// </summary>
  486. /// <param name="bmp">The WriteableBitmap.</param>
  487. /// <param name="flipMode">The flip mode.</param>
  488. /// <returns>A new WriteableBitmap that is a flipped version of the input.</returns>
  489. public static WriteableBitmap Flip(this WriteableBitmap bmp, FlipMode flipMode)
  490. {
  491. using (var context = bmp.GetBitmapContext())
  492. {
  493. // Use refs for faster access (really important!) speeds up a lot!
  494. var w = context.Width;
  495. var h = context.Height;
  496. var p = context.Pixels;
  497. var i = 0;
  498. WriteableBitmap result = null;
  499. if (flipMode == FlipMode.Horizontal)
  500. {
  501. result = BitmapFactory.New(w, h);
  502. using (var destContext = result.GetBitmapContext())
  503. {
  504. var rp = destContext.Pixels;
  505. for (var y = h - 1; y >= 0; y--)
  506. {
  507. for (var x = 0; x < w; x++)
  508. {
  509. var srcInd = y * w + x;
  510. rp[i] = p[srcInd];
  511. i++;
  512. }
  513. }
  514. }
  515. }
  516. else if (flipMode == FlipMode.Vertical)
  517. {
  518. result = BitmapFactory.New(w, h);
  519. using (var destContext = result.GetBitmapContext())
  520. {
  521. var rp = destContext.Pixels;
  522. for (var y = 0; y < h; y++)
  523. {
  524. for (var x = w - 1; x >= 0; x--)
  525. {
  526. var srcInd = y * w + x;
  527. rp[i] = p[srcInd];
  528. i++;
  529. }
  530. }
  531. }
  532. }
  533. return result;
  534. }
  535. }
  536. #endregion
  537. #endregion
  538. }
  539. }