using UnityEngine; using System; /// /// Simple implementation of image processing. /// See this link to improve this code : https://code.google.com/p/aforge/ /// For more details, see: http://wiki.unity3d.com/index.php/TextureFilter /// public static class TextureFilter { #region General private delegate void FilterMethod(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters); private static Texture2D Filter(Texture2D inTex, FilterMethod method, params object[] parameters) { float startTime = Time.realtimeSinceStartup; Color[] inPixels = null; try { inPixels = inTex.GetPixels(); } catch (UnityException e) { Debug.LogError("Error while reading the texture : " + e); return inTex; } Color[] outPixels = new Color[inPixels.Length]; if (method != null) method(inTex.width, inTex.height, inPixels, ref outPixels, parameters); float endTime = Time.realtimeSinceStartup; Debug.Log(endTime - startTime); Texture2D outTex = new Texture2D(inTex.width, inTex.height); outTex.SetPixels(outPixels); outTex.Apply(); return outTex; } private static int GetIndex(int x, int y, int width, int height) { int i = Mathf.Clamp(x, 0, width - 1) + Mathf.Clamp(y, 0, height - 1) * width; return i; } #endregion #region Grayscale public static Texture2D Grayscale(Texture2D inTex) { return Filter(inTex, GrayscaleFunc); } private static void GrayscaleFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { for (int i = 0; i < inPixels.Length; i++) { float gray = inPixels[i].grayscale; outPixels[i] = new Color(gray, gray, gray, inPixels[i].a); } } #endregion #region Threshold public static Texture2D Threshold(Texture2D inTex, float threshold) { return Filter(inTex, ThresholdFunc, threshold); } private static void ThresholdFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { float threshold = (float)parameters[0]; for (int i = 0; i < inPixels.Length; i++) { float g = inPixels[i].grayscale; outPixels[i] = g > threshold ? Color.white : Color.black; } } #endregion #region Convolutions private static float Norme(float[][] kernel) { float norme = 0f; foreach (float[] line in kernel) foreach (float f in line) norme += f; return norme; } private static float[][] Normalize(float[][] kernel) { float sum = Norme(kernel); if (!Mathf.Approximately(sum, 1f)) { for (int x = 0; x < kernel.Length; x++) for (int y = 0; y < kernel[x].Length; y++) kernel[x][y] /= sum; } else Debug.LogWarning("Kernel is already normalized"); return kernel; } public static float[][] Normalized(this float[][] kernel) { return Normalize(kernel); } #region Kernels /// /// Procedural linear kernel, filled with ones /// /// Width or length of the kernel /// Weither the kernel must be normalized /// Kernel size*size filled with ones public static float[][] LinearKernel(int size, bool normalize = true) { float[][] kernel = new float[size][]; for (int x = 0; x < size; x++) { kernel[x] = new float[size]; for (int y = 0; y < size; y++) kernel[x][y] = 1f; } if (normalize) kernel = kernel.Normalized(); return kernel; } /// /// Procedural gaussian kernel. More accurate than the const ones below, but it takes time to create it. /// /// Variance, not the square /// Width or length of the kernel /// Weither the kernel must be normalized /// Gaussian kernel public static float[][] GaussianKernel(float sigma, int size, bool normalize = true) { float[][] kernel = new float[size][]; float mean = (float)size/2; float sigmaSqr2 = sigma * sigma * 2f; float sigmaSqr2piInv = 1f / Mathf.Sqrt((float)Math.PI * sigmaSqr2); int k = size / 2; for (int x = 0; x < size; x++) { kernel[x] = new float[size]; int kx = x - k; for (int y = 0; y < size; y++) { int ky = y - k; float g = 0f; if (x > k && y < k) { g = kernel[size - x - 1][y]; } else if (x < k && y > k) { g = kernel[x][size - y - 1]; } else if (x > k && y > k) { g = kernel[size - x - 1][size - y - 1]; } else { float exp = Mathf.Exp(-((kx * kx + ky * ky) / sigmaSqr2)); g = sigmaSqr2piInv * exp; } kernel[x][y] = g; } } if (normalize) kernel = kernel.Normalized(); return kernel; } // Do not normalize public static readonly float[][] EDGEDETECT_KERNEL_1 = new float[3][] { new float[3]{ 1, 0, -1 }, new float[3]{ 0, 0, 0 }, new float[3]{ -1, 0, 1 } }; // Do not normalize public static readonly float[][] EDGEDETECT_KERNEL_2 = new float[3][] { new float[3]{ 0, 1, 0 }, new float[3]{ 1, -4, 1 }, new float[3]{ 0, 1, 0 } }; // Do not normalize // Vertical * horizontal public static readonly float[][] EDGEDETECT_KERNEL_3 = new float[3][] { new float[3]{ -1, -1, -1 }, new float[3]{ -1, 8, -1 }, new float[3]{ -1, -1, -1 } }; // Do not normalize public static readonly float[][] EDGEDETECT_KERNEL_HORIZONTAL = new float[3][] { new float[3]{ -1, -1, -1 }, new float[3]{ 2, 2, 2 }, new float[3]{ -1, -1, -1 } }; // Do not normalize public static readonly float[][] EDGEDETECT_KERNEL_VERTICAL = new float[3][] { new float[3]{ -1, 2, -1 }, new float[3]{ -1, 2, -1 }, new float[3]{ -1, 2, -1 } }; // Do not normalize public static readonly float[][] SHARPEN_KERNEL = new float[3][] { new float[3]{ 0, -1, 0 }, new float[3]{ -1, 5, -1 }, new float[3]{ 0, -1, 0 } }; // Do not normalize public static readonly float[][] LINEAR_KERNEL = new float[3][] { new float[3]{ 1f /9, 1f /9, 1f /9 }, new float[3]{ 1f /9, 1f /9, 1f /9 }, new float[3]{ 1f /9, 1f /9, 1f /9 } }; // Do not normalize public static readonly float[][] GAUSSIAN_KERNEL_3 = new float[3][] { new float[3]{ 0.07511361f, 0.1238414f, 0.07511361f }, new float[3]{ 0.1238414f, 0.20418f, 0.1238414f }, new float[3]{ 0.07511361f, 0.1238414f, 0.07511361f } }; // Do not normalize public static readonly float[][] GAUSSIAN_KERNEL_5 = new float[5][] { new float[5]{ 0.002969016f, 0.01330621f, 0.02193823f, 0.01330621f, 0.002969016f }, new float[5]{ 0.01330621f, 0.05963429f, 0.09832032f, 0.05963429f, 0.01330621f }, new float[5]{ 0.02193823f, 0.09832032f, 0.1621028f, 0.09832032f, 0.02193823f }, new float[5]{ 0.01330621f, 0.05963429f, 0.09832032f, 0.05963429f, 0.01330621f }, new float[5]{ 0.002969016f, 0.01330621f, 0.02193823f, 0.01330621f, 0.002969016f } }; // Do not normalize public static readonly float[][] GAUSSIAN_KERNEL_7 = new float[7][] { new float[7]{ 1.965191E-05f, 0.0002394093f, 0.001072958f, 0.001769009f, 0.001072958f, 0.0002394093f, 1.965191E-05f }, new float[7]{ 0.0002394093f, 0.002916602f, 0.01307131f, 0.02155094f, 0.01307131f, 0.002916602f, 0.0002394093f }, new float[7]{ 0.001072958f, 0.01307131f, 0.05858152f, 0.0965846f, 0.05858152f, 0.01307131f, 0.001072958f }, new float[7]{ 0.001769009f, 0.02155094f, 0.0965846f, 0.1592411f, 0.0965846f, 0.02155094f, 0.001769009f }, new float[7]{ 0.001072958f, 0.01307131f, 0.05858152f, 0.0965846f, 0.05858152f, 0.01307131f, 0.001072958f }, new float[7]{ 0.0002394093f, 0.002916602f, 0.01307131f, 0.02155094f, 0.01307131f, 0.002916602f, 0.0002394093f }, new float[7]{ 1.965191E-05f, 0.0002394093f, 0.001072958f, 0.001769009f, 0.001072958f, 0.0002394093f, 1.965191E-05f }, }; // Do not normalize public static readonly float[][] GAUSSIAN_KERNEL_9 = new float[9][] { new float[9]{ 1.791064E-08f, 5.931188E-07f, 7.225666E-06f, 3.238319E-05f, 5.339085E-05f, 3.238319E-05f, 7.225666E-06f, 5.931188E-07f, 1.791064E-08f }, new float[9]{ 5.931188E-07f, 1.96414E-05f, 0.0002392812f, 0.001072384f, 0.001768062f, 0.001072384f, 0.0002392812f, 1.96414E-05f, 5.931188E-07f }, new float[9]{ 7.225666E-06f, 0.0002392812f, 0.002915042f, 0.01306431f, 0.02153941f, 0.01306431f, 0.002915042f, 0.0002392812f, 7.225666E-06f }, new float[9]{ 3.238319E-05f, 0.001072384f, 0.01306431f, 0.05855018f, 0.09653293f, 0.05855018f, 0.01306431f, 0.001072384f, 3.238319E-05f }, new float[9]{ 5.339085E-05f, 0.001768062f, 0.02153941f, 0.09653293f, 0.1591559f, 0.09653293f, 0.02153941f, 0.001768062f, 5.339085E-05f }, new float[9]{ 3.238319E-05f, 0.001072384f, 0.01306431f, 0.05855018f, 0.09653293f, 0.05855018f, 0.01306431f, 0.001072384f, 3.238319E-05f }, new float[9]{ 7.225666E-06f, 0.0002392812f, 0.002915042f, 0.01306431f, 0.02153941f, 0.01306431f, 0.002915042f, 0.0002392812f, 7.225666E-06f }, new float[9]{ 5.931188E-07f, 1.96414E-05f, 0.0002392812f, 0.001072384f, 0.001768062f, 0.001072384f, 0.0002392812f, 1.96414E-05f, 5.931188E-07f }, new float[9]{ 1.791064E-08f, 5.931188E-07f, 7.225666E-06f, 3.238319E-05f, 5.339085E-05f, 3.238319E-05f, 7.225666E-06f, 5.931188E-07f, 1.791064E-08f }, }; // Do not normalize public static readonly float[][] LAPLACIAN_GAUSSIAN_KERNEL = new float[5][] { new float[5]{ 0, 0, -1, 0, 0 }, new float[5]{ 0, -1, -2, -1, 0 }, new float[5]{ -1, -2, 16, -2, -1 }, new float[5]{ 0, -1, -2, -1, 0 }, new float[5]{ 0, 0, -1, 0, 0 } }; #endregion public static Texture2D Convolution(Texture2D inTex, float[][] kernel, int iteration) { return Filter(inTex, ConvolutionFunc, kernel, iteration); } private static void ConvolutionFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { float[][] kernel = (float[][])parameters[0]; int kernelSize = kernel.Length; int iteration = (int)parameters[1]; for (int ite = 0; ite < iteration; ite++) { for (int i = 0; i < inPixels.Length; i++) { int px = i % width; int py = i / width; Color c = new Color(0f, 0f, 0f, 0f); for (int y = 0; y < kernelSize; y++) { int ky = y - kernelSize / 2; for (int x = 0; x < kernelSize; x++) { int kx = x - kernelSize / 2; c += inPixels[GetIndex(px + kx, py + ky, width, height)] * kernel[x][y]; } } outPixels[i] = c; } // Copy array for next iteration if (ite < iteration - 1) { for (int i = 0; i < inPixels.Length; i++) inPixels[i] = outPixels[i]; } } } #endregion #region Color correction #region Sepia public static Texture2D Sepia(Texture2D inTex) { return Filter(inTex, SepiaFunc); } private static void SepiaFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { for (int i = 0; i < inPixels.Length; i++) { float y = Vector3.Dot( new Vector3(0.299f, 0.587f, 0.114f), new Vector3(inPixels[i].r, inPixels[i].g, inPixels[i].b)); // Convert to Sepia Tone by adding constant Vector3 sepiaConvert = new Vector3(0.191f, -0.054f, -0.221f); Vector3 output = sepiaConvert + new Vector3(y, y, y); outPixels[i] = new Color(output.x, output.y, output.z, inPixels[i].a); } } #endregion public static Texture2D ContrasteSaturationBrightness(Texture2D inTex, float contrast, float saturation, float brightness) { return Filter(inTex, ContrasteSaturationBrightnessFunc, saturation, brightness, contrast); } private static void ContrasteSaturationBrightnessFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { float saturation = (float)parameters[0]; float brightness = (float)parameters[1]; float contrast = (float)parameters[2]; for (int i = 0; i < inPixels.Length; i++) { Color color = inPixels[i]; //RGB Color Channels float AvgLumR = .5f; float AvgLumG = .5f; float AvgLumB = .5f; //Luminace Coefficients for brightness of image Vector3 LuminaceCoeff = new Vector3(0.2125f, 0.7154f, 0.0721f); //Brigntess calculations Vector3 AvgLumin = new Vector3(AvgLumR, AvgLumG, AvgLumB); Vector3 brtColor = new Vector3(color.r, color.g, color.b) * brightness; float intensityf = Vector3.Dot(brtColor, LuminaceCoeff); Vector3 intensity = new Vector3(intensityf, intensityf, intensityf); //Saturation calculation Vector3 satColor = Vector3.Lerp(intensity, brtColor, saturation); //Contrast calculations Vector3 conColor = Vector3.Lerp(AvgLumin, satColor, contrast); outPixels[i] = new Color(conColor.x, conColor.y, conColor.z, color.a); } } #endregion #region SobelFilter public static Texture2D SobelFilter(Texture2D inTex) { return Filter(inTex, SobelFilterFunc); } // http://forum.unity3d.com/threads/5714-bumpmap-(grayscale)-to-normalmap-(rgb) private static void SobelFilterFunc(int width, int height, Color[] inPixels, ref Color[] outPixels, params object[] parameters) { for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { float xLeft = inPixels[GetIndex(x - 1, y, width, height)].grayscale; float xRight = inPixels[GetIndex(x + 1, y, width, height)].grayscale; float yUp = inPixels[GetIndex(x, y - 1, width, height)].grayscale; float yDown = inPixels[GetIndex(x, y + 1, width, height)].grayscale; float xDelta = ((xLeft - xRight) + 1) * .5f; float yDelta = ((yUp - yDown) + 1) * .5f; outPixels[GetIndex(x, y, width, height)] = new Color(xDelta, yDelta, 1f, 1f); } } } #endregion }