PageRenderTime 53ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/BitmapResampler.cs

https://bitbucket.org/rstarkov/tankiconmaker
C# | 281 lines | 212 code | 34 blank | 35 comment | 52 complexity | 8c3f0c6d9466874492794c8e495a92b8 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.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace TankIconMaker
  6. {
  7. static class BitmapResampler
  8. {
  9. public struct Contributor
  10. {
  11. /// <summary>X or Y coordinate of the contributing pixel in the source.</summary>
  12. public int Coord;
  13. public double Weight;
  14. }
  15. public struct ContributorEntry
  16. {
  17. /// <summary>Number of entries in <see cref="SrcPixel"/>.</summary>
  18. public int SrcPixelCount;
  19. /// <summary>
  20. /// All the pixels in the source image which contribute to the destination pixel, and the associated weight.
  21. /// Some of the entries at the end of the array may be unused/unpopulated; see <see cref="SrcPixelCount"/> for
  22. /// the actual count.</summary>
  23. public Contributor[] SrcPixel;
  24. }
  25. public static unsafe BitmapBase SizePos(BitmapBase source, double scaleWidth, double scaleHeight, int inX, int inY, int outX, int outY, int maxWidth = 0, int maxHeight = 0, Filter filter = null)
  26. {
  27. if (source.Width <= 0 || source.Height <= 0)
  28. return source.ToBitmapSame();
  29. PixelRect pureImg = source.PreciseSize(0);
  30. if (pureImg.Width <= 0 || pureImg.Height <= 0)
  31. return source.ToBitmapSame();
  32. int outWidth = (int) Math.Round(pureImg.Width * scaleWidth);
  33. int outHeight = (int) Math.Round(pureImg.Height * scaleHeight);
  34. if (scaleWidth == 1 && scaleHeight == 1)
  35. {
  36. //no resize needed
  37. if (inX != outX || inY != outY)
  38. {
  39. BitmapBase result;
  40. if (maxWidth == 0 && maxHeight == 0)
  41. result = new BitmapRam(outX - inX + source.Width, outY - inY + source.Height);
  42. else
  43. result = new BitmapRam(Math.Min(outX - inX + source.Width, maxWidth), Math.Min(outY - inY + source.Height, maxHeight));
  44. result.DrawImage(source, outX - inX, outY - inY);
  45. return result;
  46. }
  47. else
  48. return source.ToBitmapSame();
  49. }
  50. if (filter == null)
  51. {
  52. if (scaleWidth < 1)
  53. filter = new LanczosFilter();
  54. else
  55. filter = new MitchellFilter();
  56. }
  57. int transparentOffset;
  58. if (pureImg.Left != 0 || pureImg.Top != 0)
  59. {
  60. transparentOffset = pureImg.Left * 4 + pureImg.Top * source.Stride;
  61. // Resample looks better if transprent pixels is cropped. Especially if the image is square
  62. // Data+DataOffset, pureImg.Width, pureImg.Height instead of Data, Width, Height works like left-top cropping
  63. }
  64. else
  65. {
  66. transparentOffset = 0;
  67. }
  68. BitmapBase afterHorzResample, afterVertResample;
  69. // Horizontal resampling
  70. if (scaleWidth == 1)
  71. {
  72. afterHorzResample = source;
  73. }
  74. else
  75. {
  76. afterHorzResample = new BitmapRam(outWidth, pureImg.Height);
  77. ContributorEntry[] contrib = filter.PrecomputeResample(scaleWidth, pureImg.Width, outWidth);
  78. Resample1D(afterHorzResample, source, transparentOffset, contrib, outWidth, pureImg.Height, true);
  79. transparentOffset = 0;
  80. }
  81. // Vertical resampling
  82. if (scaleHeight == 1)
  83. {
  84. afterVertResample = afterHorzResample;
  85. }
  86. else
  87. {
  88. afterVertResample = new BitmapRam(outWidth, outHeight);
  89. ContributorEntry[] contrib = filter.PrecomputeResample(scaleHeight, pureImg.Height, outHeight);
  90. Resample1D(afterVertResample, afterHorzResample, transparentOffset, contrib, outHeight, outWidth, false);
  91. }
  92. BitmapBase final;
  93. //At this point image will be resized and moved to another BitmapBase anyway
  94. int drawX = outX - (int) Math.Round((inX - pureImg.Left) * scaleWidth);
  95. int drawY = outY - (int) Math.Round((inY - pureImg.Top) * scaleHeight);
  96. if (maxWidth == 0 && maxHeight == 0)
  97. final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight));
  98. else
  99. final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight));
  100. final.DrawImage(afterVertResample, drawX, drawY);
  101. return final;
  102. }
  103. unsafe private static void Resample1D(BitmapBase bmpDest, BitmapBase bmpSrc, int transparentOffset, ContributorEntry[] contrib, int alongSize, int crossSize, bool horz)
  104. {
  105. using (bmpSrc.UseRead())
  106. using (bmpDest.UseWrite())
  107. {
  108. byte* srcBytes = bmpSrc.Data + transparentOffset;
  109. for (int crossCoord = 0; crossCoord < crossSize; ++crossCoord)
  110. {
  111. for (int alongCoord = 0; alongCoord < alongSize; ++alongCoord)
  112. {
  113. for (int channel = 0; channel < 4; ++channel)
  114. {
  115. double intensity = 0;
  116. double wsum = 0;
  117. for (int j = 0; j < contrib[alongCoord].SrcPixelCount; j++)
  118. {
  119. int contribCoord = contrib[alongCoord].SrcPixel[j].Coord;
  120. int contribOffset = (horz ? contribCoord : crossCoord) * 4 + (horz ? crossCoord : contribCoord) * bmpSrc.Stride;
  121. double weight = contrib[alongCoord].SrcPixel[j].Weight;
  122. if (channel != 3)
  123. weight *= srcBytes[contribOffset + 3] / 255d;
  124. if (weight == 0)
  125. continue;
  126. wsum += weight;
  127. intensity += srcBytes[contribOffset + channel] * weight;
  128. }
  129. bmpDest.Data[(horz ? alongCoord : crossCoord) * 4 + (horz ? crossCoord : alongCoord) * bmpDest.Stride + channel] =
  130. (byte) Math.Min(Math.Max(intensity / wsum, byte.MinValue), byte.MaxValue);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. /// <summary>Implements a resampling filter.</summary>
  137. public abstract class Filter
  138. {
  139. public double Radius { get; protected set; }
  140. public abstract double GetValue(double x);
  141. public ContributorEntry[] PrecomputeResample(double scale, int srcWidth, int destWidth)
  142. {
  143. // all variables are named as if we're scaling horizontally for the sake of readability, but the results work for both orientations
  144. var dest = new ContributorEntry[destWidth]; // one entry for every pixel in the resulting (destination) row of pixels
  145. double r = scale < 1 ? Radius / scale : Radius; // filter radius in terms of source image pixels
  146. double s = scale < 1 ? scale : 1; // filter scale relative to source pixels
  147. for (int destX = 0; destX < destWidth; destX++)
  148. {
  149. dest[destX].SrcPixelCount = 0;
  150. dest[destX].SrcPixel = new Contributor[(int) Math.Floor(2 * r + 1)];
  151. double center = (destX + 0.5) / scale;
  152. int srcFromX = (int) Math.Floor(center - r);
  153. int srcToX = (int) Math.Ceiling(center + r);
  154. for (int srcX = srcFromX; srcX <= srcToX; srcX++)
  155. {
  156. double weight = GetValue((center - srcX - 0.5) * s);
  157. if ((weight == 0) || (srcX < 0) || (srcX >= srcWidth))
  158. continue;
  159. dest[destX].SrcPixel[dest[destX].SrcPixelCount].Coord = srcX;
  160. dest[destX].SrcPixel[dest[destX].SrcPixelCount].Weight = weight;
  161. dest[destX].SrcPixelCount++;
  162. }
  163. }
  164. return dest;
  165. }
  166. }
  167. /// <summary>
  168. /// Implements filters based on the Lanczos kernel. This includes the filters commonly known as "lanczos3" (radius
  169. /// = 3) and "sinc256" (radius = 8).</summary>
  170. public class LanczosFilter : Filter
  171. {
  172. public LanczosFilter(int radius = 3)
  173. {
  174. Radius = radius;
  175. }
  176. private double sinc(double x)
  177. {
  178. if (x == 0)
  179. return 1;
  180. x *= Math.PI;
  181. return Math.Sin(x) / x;
  182. }
  183. public override double GetValue(double x)
  184. {
  185. if (x < 0)
  186. x = -x;
  187. if (x >= Radius)
  188. return 0;
  189. return sinc(x) * sinc(x / Radius);
  190. }
  191. }
  192. /// <summary>
  193. /// Implements the family of bicubic filters, specifically the Mitchell-Netravali filters with two parameters,
  194. /// which is a generalization of the Keys cubic filters. All of these have a radius of 2.</summary>
  195. /// <remarks>
  196. /// For more information see: http://www.imagemagick.org/Usage/filter/#cubics and
  197. /// http://entropymine.com/imageworsener/bicubic/</remarks>
  198. public class BicubicFilter : Filter
  199. {
  200. public double B { get; private set; }
  201. public double C { get; private set; }
  202. /// <summary>
  203. /// Constructor. See Remarks for common values for B and C. See also <see cref="CatmullRomFilter"/> and <see
  204. /// cref="MitchellFilter"/>.</summary>
  205. /// <remarks>
  206. /// <para>
  207. /// Common values for B and C:</para>
  208. /// <list type="bullet">
  209. /// <item>Catmull_Rom, GIMP: B = 0, C = 1/2 (implemented as <see cref="CatmullRomFilter"/>)</item>
  210. /// <item>Mitchell: B = 1/3, C = 1/3 (implemented as <see cref="MitchellFilter"/>)</item>
  211. /// <item>Photoshop: B = 0, C = 3/4</item>
  212. /// <item>B-Spline: B = 1, C = 0 (aka "spline")</item>
  213. /// <item>Faststone: B = 0, C = 1</item></list></remarks>
  214. public BicubicFilter(double b, double c)
  215. {
  216. Radius = 2;
  217. B = b;
  218. C = c;
  219. }
  220. public override double GetValue(double x)
  221. {
  222. if (x < 0)
  223. x = -x;
  224. double x2 = x * x;
  225. if (x < 1)
  226. return (((12 - 9 * B - 6 * C) * (x * x2)) + ((-18 + 12 * B + 6 * C) * x2) + (6 - 2 * B)) / 6;
  227. if (x < 2)
  228. return (((-B - 6 * C) * (x * x2)) + ((6 * B + 30 * C) * x2) + ((-12 * B - 48 * C) * x) + (8 * B + 24 * C)) / 6;
  229. return 0;
  230. }
  231. }
  232. public class CatmullRomFilter : BicubicFilter
  233. {
  234. public CatmullRomFilter()
  235. : base(0, 0.5)
  236. {
  237. }
  238. }
  239. public class MitchellFilter : BicubicFilter
  240. {
  241. public MitchellFilter()
  242. : base(1 / 3.0, 1 / 3.0)
  243. {
  244. }
  245. }
  246. }
  247. }