PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/UtGraphics.cs

https://bitbucket.org/rstarkov/tankiconmaker
C# | 463 lines | 380 code | 41 blank | 42 comment | 44 complexity | a206d38f15de2c781708cd0ccbb66cc1 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause, GPL-3.0, CC-BY-SA-3.0
  1. using System;
  2. using System.ComponentModel;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Windows;
  6. using System.Windows.Media;
  7. using System.Windows.Media.Imaging;
  8. using D = System.Drawing;
  9. namespace TankIconMaker
  10. {
  11. static partial class Ut
  12. {
  13. /// <summary>Returns a new blank (transparent) GDI bitmap of the specified size.</summary>
  14. /// <param name="draw">Optionally a method to draw into the returned image.</param>
  15. public static BitmapGdi NewBitmapGdi(int width, int height, Action<D.Graphics> draw)
  16. {
  17. var result = new BitmapGdi(width, height);
  18. if (draw != null)
  19. using (var g = D.Graphics.FromImage(result.Bitmap))
  20. draw(g);
  21. return result;
  22. }
  23. /// <summary>Returns a new blank (transparent) WPF bitmap of the specified size.</summary>
  24. /// <param name="draw">A method to draw into the returned image.</param>
  25. public static BitmapSource NewBitmapWpf(int width, int height, Action<DrawingContext> draw)
  26. {
  27. var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
  28. var visual = new DrawingVisual();
  29. using (var context = visual.RenderOpen())
  30. draw(context);
  31. bmp.Render(visual);
  32. bmp.Freeze();
  33. return bmp;
  34. }
  35. public static BitmapRam ToBitmapRam(this BitmapSource src)
  36. {
  37. var result = new BitmapRam(src.PixelWidth, src.PixelHeight);
  38. result.CopyPixelsFrom(src);
  39. return result;
  40. }
  41. public static BitmapWpf ToBitmapWpf(this BitmapSource src)
  42. {
  43. var result = new BitmapWpf(src.PixelWidth, src.PixelHeight);
  44. result.CopyPixelsFrom(src);
  45. return result;
  46. }
  47. public static BitmapGdi ToBitmapGdi(this BitmapSource src)
  48. {
  49. var result = new BitmapGdi(src.PixelWidth, src.PixelHeight);
  50. result.CopyPixelsFrom(src);
  51. return result;
  52. }
  53. public static BitmapRam ToBitmapRam(this D.Image src)
  54. {
  55. return ToBitmapGdi(src).ToBitmapRam();
  56. }
  57. public static BitmapWpf ToBitmapWpf(this D.Image src)
  58. {
  59. return ToBitmapGdi(src).ToBitmapWpf();
  60. }
  61. public static BitmapGdi ToBitmapGdi(this D.Image src)
  62. {
  63. return Ut.NewBitmapGdi(src.Width, src.Height, dc => { dc.DrawImageUnscaled(src, 0, 0); });
  64. }
  65. /// <summary>Converts this value to the System.Drawing-compatible enum type.</summary>
  66. public static D.Text.TextRenderingHint ToGdi(this TextSmoothingStyle style)
  67. {
  68. switch (style)
  69. {
  70. case TextSmoothingStyle.Aliased: return D.Text.TextRenderingHint.SingleBitPerPixelGridFit;
  71. case TextSmoothingStyle.UnhintedGDI: return D.Text.TextRenderingHint.AntiAlias;
  72. case TextSmoothingStyle.AntiAliasGDI: return D.Text.TextRenderingHint.AntiAliasGridFit;
  73. case TextSmoothingStyle.ClearType: return D.Text.TextRenderingHint.ClearTypeGridFit;
  74. default: throw new Exception();
  75. }
  76. }
  77. /// <summary>
  78. /// Returns a bitmap containing the specified text drawn using the specified font and brush onto a transparent
  79. /// image. The image is sized to be as small as possible without running the risk of clipping the text.
  80. /// </summary>
  81. public static BitmapGdi TextToBitmap(this D.Graphics graphics, string text, D.Font font, D.Brush brush)
  82. {
  83. var size = graphics.MeasureString(text, font); // the default is to include any overhangs into the calculation
  84. var bmp = new BitmapGdi((int) size.Width + 1, (int) size.Height + 1);
  85. using (var g = D.Graphics.FromImage(bmp.Bitmap))
  86. {
  87. g.CompositingQuality = graphics.CompositingQuality;
  88. g.InterpolationMode = graphics.InterpolationMode;
  89. g.PixelOffsetMode = graphics.PixelOffsetMode;
  90. g.SmoothingMode = graphics.SmoothingMode;
  91. g.TextRenderingHint = graphics.TextRenderingHint;
  92. g.DrawString(text, font, brush, 0, 0);
  93. }
  94. return bmp;
  95. }
  96. /// <summary>
  97. /// Possibly the slowest ever implementation of a text draw routine, but enables pixel-perfect positioning of the text.
  98. /// </summary>
  99. /// <param name="graphics">The text is drawn into this drawing object.</param>
  100. /// <param name="text">The text to draw.</param>
  101. /// <param name="brush">The brush to use.</param>
  102. /// <param name="fontFamily">Name of the font family.</param>
  103. /// <param name="fontSize">Maximum font size: if width/height specified, text will be shrunk from this.</param>
  104. /// <param name="fontStyle">Font style in which to draw the text.</param>
  105. /// <param name="x">X coordinate of the anchor point.</param>
  106. /// <param name="y">Y coordinate of the anchor point (see also <paramref name="baseline"/>).</param>
  107. /// <param name="anchor">Specifies where the anchor point is positioned relative to the text.</param>
  108. /// <param name="width">Maximum text width, in pixels. Text is shurnk if necessary to fit in.</param>
  109. /// <param name="height">Maximum text height, in pixels. Text is shurnk if necessary to fit in.</param>
  110. /// <param name="baseline">If false, the vertical anchoring is done on the topmost/bottommost pixel. If true, top/bottom will instead position the baseline consistently,
  111. /// so that the top of the tallest letter or the bottom of the lowest descender is located on the specified pixel.</param>
  112. /// <returns>A rectangle describing the extent of the text's pixels.</returns>
  113. public static PixelRect DrawString(this D.Graphics graphics, string text, D.Brush brush, string fontFamily, double fontSize, D.FontStyle fontStyle,
  114. int x, int y, Anchor anchor, int? width = null, int? height = null, bool baseline = false)
  115. {
  116. BitmapGdi bmp;
  117. PixelRect size;
  118. double fontSizeMin = 0;
  119. double fontSizeMax = fontSize;
  120. double threshold = graphics.TextRenderingHint == D.Text.TextRenderingHint.AntiAlias ? 0.07 : 0.5;
  121. while (true)
  122. {
  123. var font = new D.Font(fontFamily, (float) fontSize, fontStyle);
  124. bmp = graphics.TextToBitmap(text, font, brush);
  125. size = baseline
  126. ? bmp.PreciseWidth().WithTopBottom(graphics.TextToBitmap("Mgy345", font, D.Brushes.White).PreciseHeight())
  127. : bmp.PreciseSize();
  128. if (width == null && height == null)
  129. break;
  130. if ((width == null || size.Width <= width.Value) && (height == null || size.Height <= height.Value))
  131. fontSizeMin = fontSize; // Fits
  132. else
  133. fontSizeMax = fontSize; // Doesn't fit
  134. if (fontSize == fontSizeMin && fontSizeMax - fontSizeMin <= threshold)
  135. break;
  136. fontSize = (fontSizeMin + fontSizeMax) / 2;
  137. }
  138. var anchr = (AnchorRaw) anchor;
  139. x -= anchr.HasFlag(AnchorRaw.Right) ? size.Width - 1 : anchr.HasFlag(AnchorRaw.Center) ? (size.Width - 1) / 2 : 0;
  140. y -= anchr.HasFlag(AnchorRaw.Bottom) ? size.Height - 1 : anchr.HasFlag(AnchorRaw.Mid) ? (size.Height - 1) / 2 : 0;
  141. graphics.DrawImageUnscaled(bmp.Bitmap, x - size.Left, y - size.Top);
  142. GC.KeepAlive(bmp);
  143. return size.Shifted(x, y);
  144. }
  145. /// <summary>A shorthand for drawing an image at coordinate 0,0.</summary>
  146. public static void DrawImage(this DrawingContext context, BitmapGdi bmp)
  147. {
  148. context.DrawImage(bmp.ToWpf(), new Rect(0, 0, bmp.Width, bmp.Height));
  149. }
  150. /// <summary>A shorthand for drawing an image at coordinate 0,0.</summary>
  151. public static void DrawImage(this DrawingContext context, BitmapSource bmp)
  152. {
  153. context.DrawImage(bmp, new Rect(0, 0, bmp.Width, bmp.Height));
  154. }
  155. /// <summary>A shorthand for drawing an image into a GDI image at coordinate 0,0.</summary>
  156. public static BitmapGdi DrawImage(this BitmapGdi target, BitmapGdi source)
  157. {
  158. using (var g = D.Graphics.FromImage(target.Bitmap))
  159. g.DrawImageUnscaled(source.Bitmap, 0, 0);
  160. return target;
  161. }
  162. public static unsafe WriteableBitmap BlendImages(WriteableBitmap imgLeft, WriteableBitmap imgRight, double rightAmount)
  163. {
  164. if (imgLeft.PixelWidth != imgRight.PixelWidth || imgLeft.PixelHeight != imgRight.PixelHeight)
  165. throw new ArgumentException();
  166. var result = new WriteableBitmap(imgLeft.PixelWidth, imgLeft.PixelHeight, 96, 96, PixelFormats.Bgra32, null);
  167. BlendImages(
  168. (byte*) imgLeft.BackBuffer, imgLeft.BackBufferStride,
  169. (byte*) imgRight.BackBuffer, imgRight.BackBufferStride,
  170. (byte*) result.BackBuffer, result.BackBufferStride, result.PixelWidth, result.PixelHeight, rightAmount);
  171. return result;
  172. }
  173. public static unsafe void BlendImages(byte* imgLeft, int strideLeft, byte* imgRight, int strideRight, byte* imgResult, int strideResult, int width, int height, double rightAmount)
  174. {
  175. var leftAmount = 1 - rightAmount;
  176. for (int y = 0; y < height; y++)
  177. {
  178. byte* ptrLeft = imgLeft + y * strideLeft;
  179. byte* ptrRight = imgRight + y * strideLeft;
  180. byte* ptrResult = imgResult + y * strideLeft;
  181. byte* endResult = ptrResult + width * 4;
  182. for (; ptrResult < endResult; ptrLeft += 4, ptrRight += 4, ptrResult += 4)
  183. {
  184. double rightRatio = blendRightRatio(*(ptrLeft + 3), *(ptrRight + 3), rightAmount);
  185. double leftRatio = 1 - rightRatio;
  186. *(ptrResult + 0) = (byte) (*(ptrLeft + 0) * leftRatio + *(ptrRight + 0) * rightRatio);
  187. *(ptrResult + 1) = (byte) (*(ptrLeft + 1) * leftRatio + *(ptrRight + 1) * rightRatio);
  188. *(ptrResult + 2) = (byte) (*(ptrLeft + 2) * leftRatio + *(ptrRight + 2) * rightRatio);
  189. *(ptrResult + 3) = (byte) (*(ptrLeft + 3) * leftAmount + *(ptrRight + 3) * rightAmount);
  190. }
  191. }
  192. }
  193. public static void SaveImage(BitmapSource image, string path, string extension)
  194. {
  195. if (extension.EqualsNoCase(".tga"))
  196. Targa.Save(image.ToBitmapRam(), path);
  197. else
  198. {
  199. var encoder = extension.EqualsNoCase(".jpg") ? new JpegBitmapEncoder()
  200. : extension.EqualsNoCase(".bmp") ? new BmpBitmapEncoder()
  201. : (BitmapEncoder) new PngBitmapEncoder();
  202. encoder.Frames.Add(BitmapFrame.Create(image));
  203. using (var file = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Read))
  204. encoder.Save(file);
  205. }
  206. }
  207. }
  208. [TypeConverter(typeof(TextSmoothingStyleTranslation.Conv))]
  209. enum TextSmoothingStyle
  210. {
  211. Aliased,
  212. AntiAliasGDI,
  213. UnhintedGDI,
  214. ClearType,
  215. }
  216. /// <summary>A better Int32Rect, expressly designed to represent pixel areas - hence the left/right/top/bottom/width/height are always "inclusive".</summary>
  217. struct PixelRect
  218. {
  219. private int _left, _width, _top, _height;
  220. /// <summary>The leftmost pixel included in the rect.</summary>
  221. public int Left { get { return _left; } }
  222. /// <summary>The topmost pixel included in the rect.</summary>
  223. public int Top { get { return _top; } }
  224. /// <summary>The rightmost pixel included in the rect.</summary>
  225. public int Right { get { return _left + _width - 1; } }
  226. /// <summary>The bottommost pixel included in the rect.</summary>
  227. public int Bottom { get { return _top + _height - 1; } }
  228. /// <summary>The total number of pixels, horizontally, included in the rect.</summary>
  229. public int Width { get { return _width; } }
  230. /// <summary>The total number of pixels, vertically, included in the rect.</summary>
  231. public int Height { get { return _height; } }
  232. /// <summary>The X coordinate of the center pixel. If the number of pixels in the rect is even, returns the pixel to the right of center.</summary>
  233. public int CenterHorz { get { return _left + _width / 2; } }
  234. /// <summary>The Y coordinate of the center pixel. If the number of pixels in the rect is even, returns the pixel to the bottom of center.</summary>
  235. public int CenterVert { get { return _top + _height / 2; } }
  236. /// <summary>The X coordinate of the center pixel. If the number of pixels in the rect is even, returns a non-integer value.</summary>
  237. public double CenterHorzD { get { return _left + _width / 2.0; } }
  238. /// <summary>The Y coordinate of the center pixel. If the number of pixels in the rect is even, returns a non-integer value.</summary>
  239. public double CenterVertD { get { return _top + _height / 2.0; } }
  240. public static PixelRect FromBounds(int left, int top, int right, int bottom)
  241. {
  242. return new PixelRect { _left = left, _top = top, _width = right - left + 1, _height = bottom - top + 1 };
  243. }
  244. public static PixelRect FromMixed(int left, int top, int width, int height)
  245. {
  246. return new PixelRect { _left = left, _top = top, _width = width, _height = height };
  247. }
  248. public static PixelRect FromLeftRight(int left, int right) { return FromBounds(left, 0, right, 0); }
  249. public static PixelRect FromTopBottom(int top, int bottom) { return FromBounds(0, top, 0, bottom); }
  250. public PixelRect WithLeftRight(int left, int right) { return FromBounds(left, Top, right, Bottom); }
  251. public PixelRect WithLeftRight(PixelRect width) { return FromBounds(width.Left, Top, width.Right, Bottom); }
  252. public PixelRect WithTopBottom(int top, int bottom) { return FromBounds(Left, top, Right, bottom); }
  253. public PixelRect WithTopBottom(PixelRect height) { return FromBounds(Left, height.Top, Right, height.Bottom); }
  254. public PixelRect Shifted(int deltaX, int deltaY) { return FromMixed(Left + deltaX, Top + deltaY, Width, Height); }
  255. }
  256. [TypeConverter(typeof(OpacityStyleTranslation.Conv))]
  257. enum OpacityStyle
  258. {
  259. Auto,
  260. MoveEndpoint,
  261. MoveMidpoint,
  262. Additive
  263. }
  264. [TypeConverter(typeof(BlurEdgeModeTranslation.Conv))]
  265. enum BlurEdgeMode
  266. {
  267. Transparent,
  268. Same,
  269. Mirror,
  270. Wrap,
  271. }
  272. [Flags]
  273. enum AnchorRaw
  274. {
  275. Left = 0x01, Center = 0x02, Right = 0x04,
  276. Top = 0x10, Mid = 0x20, Bottom = 0x40,
  277. }
  278. enum Anchor
  279. {
  280. TopLeft = AnchorRaw.Top | AnchorRaw.Left,
  281. TopCenter = AnchorRaw.Top | AnchorRaw.Center,
  282. TopRight = AnchorRaw.Top | AnchorRaw.Right,
  283. MidLeft = AnchorRaw.Mid | AnchorRaw.Left,
  284. MidCenter = AnchorRaw.Mid | AnchorRaw.Center,
  285. MidRight = AnchorRaw.Mid | AnchorRaw.Right,
  286. BottomLeft = AnchorRaw.Bottom | AnchorRaw.Left,
  287. BottomCenter = AnchorRaw.Bottom | AnchorRaw.Center,
  288. BottomRight = AnchorRaw.Bottom | AnchorRaw.Right,
  289. }
  290. class GaussianBlur
  291. {
  292. private int _radius;
  293. private int[] _kernel;
  294. private int _kernelSum;
  295. public double Radius { get; private set; }
  296. public GaussianBlur(double radius)
  297. {
  298. Radius = radius;
  299. _radius = (int) Math.Ceiling(radius);
  300. // Compute the kernel by sampling the gaussian
  301. int len = _radius * 2 + 1;
  302. double[] kernel = new double[len];
  303. double sigma = radius / 3;
  304. double sigma22 = 2 * sigma * sigma;
  305. double sigmaPi2 = 2 * Math.PI * sigma;
  306. double sqrtSigmaPi2 = (double) Math.Sqrt(sigmaPi2);
  307. double radius2 = radius * radius;
  308. double total = 0;
  309. int index = 0;
  310. for (int x = -_radius; x <= _radius; x++)
  311. {
  312. double distance = x * x;
  313. if (distance > radius2)
  314. kernel[index] = 0;
  315. else
  316. kernel[index] = Math.Exp(-distance / sigma22) / sqrtSigmaPi2;
  317. total += kernel[index];
  318. index++;
  319. }
  320. // Convert to integers
  321. _kernel = new int[len];
  322. _kernelSum = 0;
  323. double scale = 2147483647.0 / (255 * total * len); // scale so that the integer total can never overflow
  324. scale /= 5; // there will be rounding errors; make sure we don’t overflow even then
  325. for (int i = 0; i < len; i++)
  326. {
  327. _kernel[i] = (int) (kernel[i] * scale);
  328. _kernelSum += _kernel[i];
  329. }
  330. }
  331. internal unsafe void Horizontal(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode)
  332. {
  333. for (int y = 0; y < src.Height; y++)
  334. {
  335. byte* rowSource = src.Data + y * src.Stride;
  336. byte* rowResult = dest.Data + y * dest.Stride;
  337. for (int x = 0; x < src.Width; x++)
  338. {
  339. int rSum = 0, gSum = 0, bSum = 0, aSum = 0;
  340. for (int k = 0, xSrc = x - _kernel.Length / 2; k < _kernel.Length; k++, xSrc++)
  341. {
  342. int xRead = xSrc;
  343. if (xRead < 0 || xRead >= src.Width)
  344. switch (edgeMode)
  345. {
  346. case BlurEdgeMode.Transparent:
  347. continue;
  348. case BlurEdgeMode.Same:
  349. xRead = xRead < 0 ? 0 : src.Width - 1;
  350. break;
  351. case BlurEdgeMode.Wrap:
  352. xRead = Ut.ModPositive(xRead, src.Width);
  353. break;
  354. case BlurEdgeMode.Mirror:
  355. if (xRead < 0)
  356. xRead = -xRead - 1;
  357. xRead = xRead % (2 * src.Width);
  358. if (xRead >= src.Width)
  359. xRead = 2 * src.Width - xRead - 1;
  360. break;
  361. }
  362. xRead <<= 2; // * 4
  363. bSum += _kernel[k] * rowSource[xRead + 0];
  364. gSum += _kernel[k] * rowSource[xRead + 1];
  365. rSum += _kernel[k] * rowSource[xRead + 2];
  366. aSum += _kernel[k] * rowSource[xRead + 3];
  367. }
  368. int xWrite = x << 2; // * 4
  369. rowResult[xWrite + 0] = (byte) (bSum / _kernelSum);
  370. rowResult[xWrite + 1] = (byte) (gSum / _kernelSum);
  371. rowResult[xWrite + 2] = (byte) (rSum / _kernelSum);
  372. rowResult[xWrite + 3] = (byte) (aSum / _kernelSum);
  373. }
  374. }
  375. }
  376. internal unsafe void Vertical(BitmapBase src, BitmapBase dest, BlurEdgeMode edgeMode)
  377. {
  378. for (int x = 0; x < src.Width; x++)
  379. {
  380. byte* colSource = src.Data + x * 4;
  381. byte* colResult = dest.Data + x * 4;
  382. for (int y = 0; y < src.Height; y++)
  383. {
  384. int rSum = 0, gSum = 0, bSum = 0, aSum = 0;
  385. for (int k = 0, ySrc = y - _kernel.Length / 2; k < _kernel.Length; k++, ySrc++)
  386. {
  387. int yRead = ySrc;
  388. if (yRead < 0 || yRead >= src.Height)
  389. switch (edgeMode)
  390. {
  391. case BlurEdgeMode.Transparent:
  392. continue;
  393. case BlurEdgeMode.Same:
  394. yRead = yRead < 0 ? 0 : src.Height - 1;
  395. break;
  396. case BlurEdgeMode.Wrap:
  397. yRead = Ut.ModPositive(yRead, src.Height);
  398. break;
  399. case BlurEdgeMode.Mirror:
  400. if (yRead < 0)
  401. yRead = -yRead - 1;
  402. yRead = yRead % (2 * src.Height);
  403. if (yRead >= src.Height)
  404. yRead = 2 * src.Height - yRead - 1;
  405. break;
  406. }
  407. yRead *= src.Stride;
  408. bSum += _kernel[k] * colSource[yRead + 0];
  409. gSum += _kernel[k] * colSource[yRead + 1];
  410. rSum += _kernel[k] * colSource[yRead + 2];
  411. aSum += _kernel[k] * colSource[yRead + 3];
  412. }
  413. int yWrite = y * dest.Stride;
  414. colResult[yWrite + 0] = (byte) (bSum / _kernelSum);
  415. colResult[yWrite + 1] = (byte) (gSum / _kernelSum);
  416. colResult[yWrite + 2] = (byte) (rSum / _kernelSum);
  417. colResult[yWrite + 3] = (byte) (aSum / _kernelSum);
  418. }
  419. }
  420. }
  421. }
  422. }