Image Extension Methods

ImageExtensionMethods

Unity3D – I find myself often writing little reusable methods for the built in objects in Unity. I would commonly build static utility classes for do these operations.  It becomes a problem when you or your team does not know about these methods and starts in-lining or creating duplicates of these same operations. After reading a blog post on Gamasutra by Josh Sutphin Adding to Unity’s Built-In Classes Using Extension Methods I found extension methods to be a natural fit for these situations.

For me writing art tools there were a few functions that I always wanted to have handy when it comes to reading the pixels of textures. This is especially true when you want to scale the texture as unity can only read pixel(s) from a mip level or get the color at a u,v cord. I also found the Texture2D.Resize() function to be a bit strange and not do what I would want or expect of it.

Texture2DExtensionMethods.cs

/// <summary>
/// Texture2D extension methods.
/// www.noobpaint.com
/// </summary>
using UnityEngine;
using System.Collections;

public static class Texture2DExtensionMethods
{
  /// <summary>
  /// Gets the pixel centers of a texture.
  /// </summary>
  /// <returns>The pixel centers.</returns>
  /// <param name="texture2D">Texture2 d.</param>
  public static float[,] GetPixelCenters(this Texture2D texture2D)
  {
    return GetPixelCenters(texture2D, texture2D.width, 
    texture2D.height);
  }

  /// <summary>
  /// Gets the pixel centers of texture size width by height.
  /// </summary>
  /// <returns>The pixel centers.</returns>
  /// <param name="texture2D">Texture2 d.</param>
  /// <param name="width">Width.</param>
  /// <param name="height">Height.</param>
  public static float[,] GetPixelCenters(this Texture2D texture2D, 
  int width, int height)
  {
    //generate array of offsets for get pixels
    //get uv coord at center of pixel for sampling
    float xStep = 1.0f / (width*2.0f);
    float yStep = 1.0f / (height*2.0f);
    int N = width * height;

    //generate x centers
    float[] xSteps = new float[width];
    for (int i = 0; i < width; i++)
    {
      xSteps[i] = (i * xStep * 2) + xStep;
    }
    //generate y centers 
    float[] ySteps = new float[height];
    for (int i = 0; i < height; i++)
    {
      ySteps[i] = (i * yStep * 2) + yStep;
    }
    //array of float offsets starting at bottom left going right
    //bottom to top
    float[,] offsetArray = new float[N,2];
    for (int i = 0; i < N; i++)
    {
      int x = i % width;
      int y = (i - x) / width;

      offsetArray[i,0] = xSteps[x];
      offsetArray[i,1] = ySteps[y];
    }
    return offsetArray;
  }

  /// <summary>
  /// Gets the pixels using bilinear filtering
  /// to flattened 2D array, where pixels are laid 
  /// out left to right, bottom to top.
  /// </summary>
  /// <returns>The pixels bilinear.</returns>
  /// <param name="texture">Texture.</param>
  public static Color[] GetPixelsBilinear(this Texture2D texture)
  {
    return GetPixelsBilinear(texture, texture.width, texture.height);
  }

  /// <summary>
  /// Gets the pixels using bilinear filtering
  /// to flattened 2D array, where pixels are laid 
  /// out left to right, bottom to top.
  /// </summary>
  /// <returns>The pixels bilinear.</returns>
  /// <param name="texture">Texture.</param>
  /// <param name="width">Width.</param>
  /// <param name="height">Height.</param>
  public static Color[] GetPixelsBilinear(this Texture2D texture, 
  int width, int height)
  {
    int N = width * height;

    Color[] pixels = new Color[N];

    float[,] offsetArray = texture.GetPixelCenters(width,height);

    for (int i = 0; i < N; i++)
    {
      pixels[i] = texture.GetPixelBilinear(offsetArray[i,0], 
      offsetArray[i,1]);
    }
    return pixels;
  }

  /// <summary>
  /// Resizes Texture and fills with get pixels bilinear.
  /// </summary>
  /// <returns>The and fill.</returns>
  /// <param name="texture">Texture.</param>
  /// <param name="width">Width.</param>
  /// <param name="height">Height.</param>
  public static Texture2D ResizeAndFill(this Texture2D texture, 
  int width, int height)
  {
    int N = width * height;

    Color[] pixels = new Color[N];

    pixels = texture.GetPixelsBilinear(width, height);

    texture.Resize(width, height);

    texture.SetPixels(pixels);
    texture.Apply();

    return texture;
  }

  public static Texture2D Copy(this Texture2D texture)
  {
    return Object.Instantiate<Texture2D>(texture);
  }
}

The combination of being able to have GetPixelBilinear() grab from the pixel’s center with GetPixelCenters() allows me to then build a for loop function to return my colors in GetPixelsBilinear().  This also lets me create the ResizeAndFill() function that gives me the scaling functions I was wanting from the Texture2D.Resize() function.

I hope you find these functions useful. As of now scaling an image down significantly can leave it with an unwanted lose in quality. I plan on adding a way to use a multi tap blur as you shrink the image and to add sharpen steps back in. Also, want to look at adding functions to custom encode or adjust your mip levels.