/BitmapResampler.cs
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
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
-
- namespace TankIconMaker
- {
- static class BitmapResampler
- {
- public struct Contributor
- {
- /// <summary>X or Y coordinate of the contributing pixel in the source.</summary>
- public int Coord;
- public double Weight;
- }
-
- public struct ContributorEntry
- {
- /// <summary>Number of entries in <see cref="SrcPixel"/>.</summary>
- public int SrcPixelCount;
- /// <summary>
- /// All the pixels in the source image which contribute to the destination pixel, and the associated weight.
- /// Some of the entries at the end of the array may be unused/unpopulated; see <see cref="SrcPixelCount"/> for
- /// the actual count.</summary>
- public Contributor[] SrcPixel;
- }
-
- 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)
- {
- if (source.Width <= 0 || source.Height <= 0)
- return source.ToBitmapSame();
-
- PixelRect pureImg = source.PreciseSize(0);
- if (pureImg.Width <= 0 || pureImg.Height <= 0)
- return source.ToBitmapSame();
-
- int outWidth = (int) Math.Round(pureImg.Width * scaleWidth);
- int outHeight = (int) Math.Round(pureImg.Height * scaleHeight);
-
- if (scaleWidth == 1 && scaleHeight == 1)
- {
- //no resize needed
- if (inX != outX || inY != outY)
- {
- BitmapBase result;
- if (maxWidth == 0 && maxHeight == 0)
- result = new BitmapRam(outX - inX + source.Width, outY - inY + source.Height);
- else
- result = new BitmapRam(Math.Min(outX - inX + source.Width, maxWidth), Math.Min(outY - inY + source.Height, maxHeight));
-
- result.DrawImage(source, outX - inX, outY - inY);
- return result;
- }
- else
- return source.ToBitmapSame();
- }
-
- if (filter == null)
- {
- if (scaleWidth < 1)
- filter = new LanczosFilter();
- else
- filter = new MitchellFilter();
- }
-
- int transparentOffset;
- if (pureImg.Left != 0 || pureImg.Top != 0)
- {
- transparentOffset = pureImg.Left * 4 + pureImg.Top * source.Stride;
- // Resample looks better if transprent pixels is cropped. Especially if the image is square
- // Data+DataOffset, pureImg.Width, pureImg.Height instead of Data, Width, Height works like left-top cropping
- }
- else
- {
- transparentOffset = 0;
- }
-
- BitmapBase afterHorzResample, afterVertResample;
-
- // Horizontal resampling
- if (scaleWidth == 1)
- {
- afterHorzResample = source;
- }
- else
- {
- afterHorzResample = new BitmapRam(outWidth, pureImg.Height);
- ContributorEntry[] contrib = filter.PrecomputeResample(scaleWidth, pureImg.Width, outWidth);
- Resample1D(afterHorzResample, source, transparentOffset, contrib, outWidth, pureImg.Height, true);
- transparentOffset = 0;
- }
-
- // Vertical resampling
- if (scaleHeight == 1)
- {
- afterVertResample = afterHorzResample;
- }
- else
- {
- afterVertResample = new BitmapRam(outWidth, outHeight);
- ContributorEntry[] contrib = filter.PrecomputeResample(scaleHeight, pureImg.Height, outHeight);
- Resample1D(afterVertResample, afterHorzResample, transparentOffset, contrib, outHeight, outWidth, false);
- }
-
- BitmapBase final;
- //At this point image will be resized and moved to another BitmapBase anyway
- int drawX = outX - (int) Math.Round((inX - pureImg.Left) * scaleWidth);
- int drawY = outY - (int) Math.Round((inY - pureImg.Top) * scaleHeight);
- if (maxWidth == 0 && maxHeight == 0)
- final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight));
- else
- final = new BitmapRam(Math.Max(drawX + outWidth, maxWidth), Math.Max(drawY + outHeight, maxHeight));
- final.DrawImage(afterVertResample, drawX, drawY);
- return final;
- }
-
- unsafe private static void Resample1D(BitmapBase bmpDest, BitmapBase bmpSrc, int transparentOffset, ContributorEntry[] contrib, int alongSize, int crossSize, bool horz)
- {
- using (bmpSrc.UseRead())
- using (bmpDest.UseWrite())
- {
- byte* srcBytes = bmpSrc.Data + transparentOffset;
- for (int crossCoord = 0; crossCoord < crossSize; ++crossCoord)
- {
- for (int alongCoord = 0; alongCoord < alongSize; ++alongCoord)
- {
- for (int channel = 0; channel < 4; ++channel)
- {
- double intensity = 0;
- double wsum = 0;
-
- for (int j = 0; j < contrib[alongCoord].SrcPixelCount; j++)
- {
- int contribCoord = contrib[alongCoord].SrcPixel[j].Coord;
- int contribOffset = (horz ? contribCoord : crossCoord) * 4 + (horz ? crossCoord : contribCoord) * bmpSrc.Stride;
- double weight = contrib[alongCoord].SrcPixel[j].Weight;
-
- if (channel != 3)
- weight *= srcBytes[contribOffset + 3] / 255d;
-
- if (weight == 0)
- continue;
-
- wsum += weight;
- intensity += srcBytes[contribOffset + channel] * weight;
- }
-
- bmpDest.Data[(horz ? alongCoord : crossCoord) * 4 + (horz ? crossCoord : alongCoord) * bmpDest.Stride + channel] =
- (byte) Math.Min(Math.Max(intensity / wsum, byte.MinValue), byte.MaxValue);
- }
- }
- }
- }
- }
-
- /// <summary>Implements a resampling filter.</summary>
- public abstract class Filter
- {
- public double Radius { get; protected set; }
- public abstract double GetValue(double x);
-
- public ContributorEntry[] PrecomputeResample(double scale, int srcWidth, int destWidth)
- {
- // all variables are named as if we're scaling horizontally for the sake of readability, but the results work for both orientations
- var dest = new ContributorEntry[destWidth]; // one entry for every pixel in the resulting (destination) row of pixels
- double r = scale < 1 ? Radius / scale : Radius; // filter radius in terms of source image pixels
- double s = scale < 1 ? scale : 1; // filter scale relative to source pixels
-
- for (int destX = 0; destX < destWidth; destX++)
- {
- dest[destX].SrcPixelCount = 0;
- dest[destX].SrcPixel = new Contributor[(int) Math.Floor(2 * r + 1)];
- double center = (destX + 0.5) / scale;
- int srcFromX = (int) Math.Floor(center - r);
- int srcToX = (int) Math.Ceiling(center + r);
-
- for (int srcX = srcFromX; srcX <= srcToX; srcX++)
- {
- double weight = GetValue((center - srcX - 0.5) * s);
-
- if ((weight == 0) || (srcX < 0) || (srcX >= srcWidth))
- continue;
-
- dest[destX].SrcPixel[dest[destX].SrcPixelCount].Coord = srcX;
- dest[destX].SrcPixel[dest[destX].SrcPixelCount].Weight = weight;
- dest[destX].SrcPixelCount++;
- }
- }
- return dest;
- }
- }
-
- /// <summary>
- /// Implements filters based on the Lanczos kernel. This includes the filters commonly known as "lanczos3" (radius
- /// = 3) and "sinc256" (radius = 8).</summary>
- public class LanczosFilter : Filter
- {
- public LanczosFilter(int radius = 3)
- {
- Radius = radius;
- }
-
- private double sinc(double x)
- {
- if (x == 0)
- return 1;
- x *= Math.PI;
- return Math.Sin(x) / x;
- }
-
- public override double GetValue(double x)
- {
- if (x < 0)
- x = -x;
- if (x >= Radius)
- return 0;
- return sinc(x) * sinc(x / Radius);
- }
- }
-
- /// <summary>
- /// Implements the family of bicubic filters, specifically the Mitchell-Netravali filters with two parameters,
- /// which is a generalization of the Keys cubic filters. All of these have a radius of 2.</summary>
- /// <remarks>
- /// For more information see: http://www.imagemagick.org/Usage/filter/#cubics and
- /// http://entropymine.com/imageworsener/bicubic/</remarks>
- public class BicubicFilter : Filter
- {
- public double B { get; private set; }
- public double C { get; private set; }
-
- /// <summary>
- /// Constructor. See Remarks for common values for B and C. See also <see cref="CatmullRomFilter"/> and <see
- /// cref="MitchellFilter"/>.</summary>
- /// <remarks>
- /// <para>
- /// Common values for B and C:</para>
- /// <list type="bullet">
- /// <item>Catmull_Rom, GIMP: B = 0, C = 1/2 (implemented as <see cref="CatmullRomFilter"/>)</item>
- /// <item>Mitchell: B = 1/3, C = 1/3 (implemented as <see cref="MitchellFilter"/>)</item>
- /// <item>Photoshop: B = 0, C = 3/4</item>
- /// <item>B-Spline: B = 1, C = 0 (aka "spline")</item>
- /// <item>Faststone: B = 0, C = 1</item></list></remarks>
- public BicubicFilter(double b, double c)
- {
- Radius = 2;
- B = b;
- C = c;
- }
-
- public override double GetValue(double x)
- {
- if (x < 0)
- x = -x;
- double x2 = x * x;
- if (x < 1)
- return (((12 - 9 * B - 6 * C) * (x * x2)) + ((-18 + 12 * B + 6 * C) * x2) + (6 - 2 * B)) / 6;
- if (x < 2)
- return (((-B - 6 * C) * (x * x2)) + ((6 * B + 30 * C) * x2) + ((-12 * B - 48 * C) * x) + (8 * B + 24 * C)) / 6;
- return 0;
- }
- }
-
- public class CatmullRomFilter : BicubicFilter
- {
- public CatmullRomFilter()
- : base(0, 0.5)
- {
- }
- }
-
- public class MitchellFilter : BicubicFilter
- {
- public MitchellFilter()
- : base(1 / 3.0, 1 / 3.0)
- {
- }
- }
-
- }
- }