/// <summary>
        /// Creates average intensity Color32 array.
        /// </summary>
        /// <param name="colors">Source Color32 array.</param>
        /// <returns>Average intensity Color32 array.</returns>
        public static Color32[] MakeAverageIntensity(Color32[] colors)
        {
            Color32[] newColors = (Color32[])colors.Clone();

            // Make each pixel grayscale based on average intensity
            for (int i = 0; i < colors.Length; i++)
            {
                // Get average intensity
                Color32 srcColor = colors[i];
                int intensity = (srcColor.r + srcColor.g + srcColor.b) / 3;
                Color32 dstColor = new Color32((byte)intensity, (byte)intensity, (byte)intensity, srcColor.a);
                newColors[i] = dstColor;
            }

            return newColors;
        }
            /// <summary>
            /// Applies filter to input Color32 array.
            /// </summary>
            /// <param name="colors">Source Color32 array.</param>
            /// <param name="width">Image width.</param>
            /// <param name="height">Image height.</param>
            /// <returns>New Color32 array with filter applied.</returns>
            public Color32[] ApplyFilter(Color32[] colors, int width, int height)
            {
                Color32[] newColors = (Color32[])colors.Clone();
                for (int x = 0; x < width; ++x)
                {
                    for (int y = 0; y < height; ++y)
                    {
                        int weight = 0;
                        int r = 0, g = 0, b = 0, a = 0;

                        int curX = -FilterWidth / 2;
                        for (int x2 = 0; x2 < FilterWidth; ++x2)
                        {
                            int sampleX = curX + x;                     // Sample point is wrapped to avoid seams in edge-finding filters
                            if (sampleX < 0) sampleX = width - 1;
                            if (sampleX >= width) sampleX = 0;

                            int curY = -FilterHeight / 2;
                            for (int y2 = 0; y2 < FilterHeight; ++y2)
                            {
                                int sampleY = curY + y;                 // Sample point is wrapped to avoid seams in edge-finding filters
                                if (sampleY < 0) sampleY = height - 1;
                                if (sampleY >= height) sampleY = 0;

                                Color32 pixel = colors[sampleY * width + sampleX];
                                r += MyFilter[x2, y2] * pixel.r;
                                g += MyFilter[x2, y2] * pixel.g;
                                b += MyFilter[x2, y2] * pixel.b;
                                a = pixel.a;
                                weight += MyFilter[x2, y2];

                                ++curY;
                            }

                            ++curX;
                        }

                        Color32 meanPixel = colors[y * width + x];
                        if (weight == 0)
                            weight = 1;
                        if (weight > 0)
                        {
                            if (Absolute)
                            {
                                r = System.Math.Abs(r);
                                g = System.Math.Abs(g);
                                b = System.Math.Abs(b);
                            }

                            r = (r / weight) + Offset;
                            r = Clamp(r, 0, 255);
                            g = (g / weight) + Offset;
                            g = Clamp(g, 0, 255);
                            b = (b / weight) + Offset;
                            b = Clamp(b, 0, 255);
                            meanPixel = new Color32((byte)r, (byte)g, (byte)b, (byte)a);
                        }

                        newColors[y * width + x] = meanPixel;
                    }
                }

                return newColors;
            }
        /// <summary>
        /// Converts bump map to normal map.
        /// </summary>
        /// <param name="colors">Source Color32 array.</param>
        /// <param name="strength">Normal strength.</param>
        /// <returns>Color32[] normal map.</returns>
        public static Color32[] ConvertBumpToNormals(ref Color32[] colors, int width, int height, float strength = 1)
        {
            Color32[] newColors = (Color32[])colors.Clone();
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    // Look up the heights to either side of this pixel
                    float left = GetIntensity(ref colors, x - 1, y, width, height);
                    float right = GetIntensity(ref colors, x + 1, y, width, height);
                    float top = GetIntensity(ref colors, x, y - 1, width, height);
                    float bottom = GetIntensity(ref colors, x, y + 1, width, height);

                    // Compute gradient vectors, then cross them to get the normal
                    Vector3 dx = new Vector3(1, 0, (right - left) * strength);
                    Vector3 dy = new Vector3(0, 1, (bottom - top) * strength);
                    Vector3 normal = Vector3.Cross(dx, dy);
                    normal.Normalize();

                    // Store result packed for Unity
                    byte r = (byte)((normal.x + 1.0f) * 127.5f);
                    byte g = (byte)(255 - ((normal.y + 1.0f) * 127.5f));
                    newColors[y * width + x] = new Color32(g, g, g, r);

                    //// This is a standard normal texture without Unity packing
                    //newColors[y * width + x] = new Color32(
                    //    (byte)((normal.x + 1.0f) * 127.5f),
                    //    (byte)(255 - ((normal.y + 1.0f) * 127.5f)),
                    //    (byte)((normal.z + 1.0f) * 127.5f),
                    //    0xff);
                }
            }

            return newColors;
        }