text: (function () {
    var ns = $.namespace('pskl.utils');
    var colorCache = {};
    var offCanvasPool = {};
    var imageDataPool = {};
    ns.FrameUtils = {
      /**
       * Render a Frame object as an image.
       * Can optionally scale it (zoom)
       * @param frame {Frame} frame
       * @param zoom {Number} zoom
       * @return {Image}
       */
      toImage : function (frame, zoom, opacity) {
        zoom = zoom || 1;
        opacity = isNaN(opacity) ? 1 : opacity;
  
        var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom);
        canvasRenderer.drawTransparentAs(Constants.TRANSPARENT_COLOR);
        canvasRenderer.setOpacity(opacity);
        return canvasRenderer.render();
      },
  
      /**
       * Draw the provided frame in a 2d canvas
       *
       * @param {Frame|RenderedFrame} frame the frame to draw
       * @param {Canvas} canvas the canvas target
       * @param {String} transparentColor (optional) color to use to represent transparent pixels.
       * @param {String} globalAlpha (optional) global frame opacity
       */
      drawToCanvas : function (frame, canvas, transparentColor, globalAlpha) {
        var context = canvas.getContext('2d');
        globalAlpha = isNaN(globalAlpha) ? 1 : globalAlpha;
        context.globalAlpha = globalAlpha;
        transparentColor = transparentColor || Constants.TRANSPARENT_COLOR;
  
        if (frame instanceof pskl.model.frame.RenderedFrame) {
          context.fillRect(transparentColor, 0, 0, frame.getWidth(), frame.getHeight());
          context.drawImage(frame.getRenderedFrame(), 0, 0);
        } else {
          var w = frame.getWidth();
          var h = frame.getHeight();
          var pixels = frame.pixels;
  
          // Replace transparent color
          var constantTransparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
          var transparentColorInt = pskl.utils.colorToInt(transparentColor);
          if (transparentColorInt != constantTransparentColorInt) {
            pixels = frame.getPixels();
            for (var i = 0, length = pixels.length; i < length; i++) {
              if (pixels[i] == constantTransparentColorInt) {
                pixels[i] = transparentColorInt;
              }
            }
          }
  
          // Imagedata from cache
          var imageDataKey = w + '-' + h;
          var imageData;
          if (!imageDataPool[imageDataKey]) {
            imageData = imageDataPool[imageDataKey] = context.createImageData(w, h);
          } else {
            imageData = imageDataPool[imageDataKey];
          }
  
          // Convert to uint8 and set the data
          var data = new Uint8ClampedArray(pixels.buffer);
          var imgDataData = imageData.data;
          imgDataData.set(data);
  
          // Offcanvas from cache
          var offCanvasKey = w + '-' + h;
          var offCanvas;
          if (!offCanvasPool[offCanvasKey]) {
            offCanvas = offCanvasPool[offCanvasKey] = pskl.utils.CanvasUtils.createCanvas(w, h);
            offCanvas.context = offCanvas.getContext('2d');
          } else {
            offCanvas = offCanvasPool[offCanvasKey];
          }
  
          // Put pixel data to offcanvas and draw the offcanvas onto the canvas
          offCanvas.context.putImageData(imageData, 0, 0);
          context.drawImage(offCanvas, 0, 0, w, h);
          context.globalAlpha = 1;
        }
      },
  
      /**
       * Render a line of a single color in a given canvas 2D context.
       *
       * @param  color {String} color to draw
       * @param  x {Number} x coordinate
       * @param  y {Number} y coordinate
       * @param  width {Number} width of the line to draw, in pixels
       * @param  context {CanvasRenderingContext2D} context of the canvas target
       */
      renderLine_ : function (color, x, y, width, context) {
        if (color === Constants.TRANSPARENT_COLOR || color === null) {
          return;
        }
        context.fillStyle = color;
        context.fillRect(x, y, 1, width);
      },
  
      merge : function (frames) {
        var merged = null;
        if (frames.length) {
          merged = frames[0].clone();
          for (var i = 1 ; i < frames.length ; i++) {
            pskl.utils.FrameUtils.mergeFrames_(merged, frames[i]);
          }
        }
        return merged;
      },
  
      mergeFrames_ : function (frameA, frameB) {
        var transparentColorInt = pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR);
        for (var i = 0, length = frameA.getWidth() * frameA.getHeight(); i < length; ++i) {
          if (frameB.pixels[i] != transparentColorInt && frameA.pixels[i] != frameB.pixels[i]) {
            frameA.pixels[i] = frameB.pixels[i];
          }
        }
      },
  
      /**
       * Insert the provided image in the provided frame, at the x, y sprite coordinates.
       * Coordinates will be adjusted so that the image fits in the frame, if possible.
       */
      addImageToFrame: function (frame, image, x, y) {
        var imageFrame = pskl.utils.FrameUtils.createFromImage(image);
        x = x - Math.floor(imageFrame.width / 2);
        y = y - Math.floor(imageFrame.height / 2);
  
        x = Math.max(0, x);
        y = Math.max(0, y);
  
        if (imageFrame.width <= frame.width) {
          x = Math.min(x, frame.width - imageFrame.width);
        }
  
        if (imageFrame.height <= frame.height) {
          y = Math.min(y, frame.height - imageFrame.height);
        }
  
        imageFrame.forEachPixel(function (color, frameX, frameY) {
          if (color != pskl.utils.colorToInt(Constants.TRANSPARENT_COLOR)) {
            frame.setPixel(x + frameX, y + frameY, color);
          }
        });
      },
  
      resize : function (frame, targetWidth, targetHeight, smoothing) {
        var image = pskl.utils.FrameUtils.toImage(frame);
        var resizedImage = pskl.utils.ImageResizer.resize(image, targetWidth, targetHeight, smoothing);
        return pskl.utils.FrameUtils.createFromImage(resizedImage);
      },
  
      removeTransparency : function (frame) {
        frame.forEachPixel(function (color, x, y) {
          var alpha = color >> 24 >>> 0 & 0xff;
          if (alpha && alpha !== 255) {
            var rounded = Math.round(alpha / 255) * 255;
            var roundedColor = color - (alpha << 24 >>> 0) + (rounded << 24 >>> 0);
            frame.setPixel(x, y, roundedColor);
          }
        });
      },
  
      createFromCanvas : function (canvas, x, y, w, h, preserveOpacity) {
        var imgData = canvas.getContext('2d').getImageData(x, y, w, h).data;
        return pskl.utils.FrameUtils.createFromImageData_(imgData, w, h, preserveOpacity);
      },
  
      createFromImageSrc : function (src, preserveOpacity, cb) {
        var image = new Image();
        image.addEventListener('load', function onImageLoaded() {
          image.removeEventListener('load', onImageLoaded);
          var frame = ns.FrameUtils.createFromImage(image, preserveOpacity);
          cb(frame);
        });
        image.src = src;
      },
  
      /*
       * Create a pskl.model.Frame from an Image object. By default transparent
       * pixels will be converted to completely opaque or completely transparent
       * pixels. If preserveOpacity is true the actual opacity of the pixel will
       * be used and the generated frame will contain rgba pixels.
       *
       * @param  {Image} image source image
       * @param  {boolean} preserveOpacity set to true to preserve the opacity
       * @return {pskl.model.Frame} corresponding frame
       */
      createFromImage : function (image, preserveOpacity) {
        var w = image.width;
        var h = image.height;
        var canvas = pskl.utils.CanvasUtils.createCanvas(w, h);
        var context = canvas.getContext('2d');
  
        context.drawImage(image, 0, 0, w, h, 0, 0, w, h);
        var imgData = context.getImageData(0, 0, w, h).data;
        return pskl.utils.FrameUtils.createFromImageData_(imgData, w, h, preserveOpacity);
      },
  
      createFromImageData_ : function (imageData, width, height, preserveOpacity) {
        var frame = new pskl.model.Frame(width, height);
        frame.pixels = new Uint32Array(imageData.buffer);
        if (!preserveOpacity) {
          pskl.utils.FrameUtils.removeTransparency(frame);
        }
  
        return frame;
      },
  
      /**
       * Create a Frame array from an Image object.
       * Transparent pixels will either be converted to completely opaque or completely transparent pixels.
       *
       * @param  {Image} image source image
       * @param  {Number} frameCount number of frames in the spritesheet
       * @return {Array<Frame>}
       */
      createFramesFromSpritesheet : function (image, frameCount) {
        var layout = [];
        for (var i = 0 ; i < frameCount ; i++) {
          layout.push([i]);
        }
        var chunkFrames = pskl.utils.FrameUtils.createFramesFromChunk(image, layout);
        return chunkFrames.map(function (chunkFrame) {
          return chunkFrame.frame;
        });
      },
  
      /**
       * Create a Frame array from an Image object.
       * Transparent pixels will either be converted to completely opaque or completely transparent pixels.
       *
       * @param  {Image} image source image
       * @param  {Array <Array>} layout description of the frame indexes expected to be found in the chunk
       * @return {Array<Object>} array of objects containing: {index: frame index, frame: frame instance}
       */
      createFramesFromChunk : function (image, layout) {
        var width = image.width;
        var height = image.height;
  
        // Recalculate the expected frame dimensions from the layout information
        var frameWidth = width / layout.length;
        var frameHeight = height / layout[0].length;
  
        // Create a canvas adapted to the image size
        var canvas = pskl.utils.CanvasUtils.createCanvas(frameWidth, frameHeight);
        var context = canvas.getContext('2d');
  
        // Draw the zoomed-up pixels to a different canvas context
        var chunkFrames = [];
        for (var i = 0 ; i < layout.length ; i++) {
          var row = layout[i];
          for (var j = 0 ; j < row.length ; j++) {
            context.clearRect(0, 0 , frameWidth, frameHeight);
            context.drawImage(image, frameWidth * i, frameHeight * j,
              frameWidth, frameHeight, 0, 0, frameWidth, frameHeight);
            var frame = pskl.utils.FrameUtils.createFromCanvas(canvas, 0, 0, frameWidth, frameHeight);
            chunkFrames.push({
              index : layout[i][j],
              frame : frame
            });
          }
        }
  
        return chunkFrames;
      },
  
      toFrameGrid : function (normalGrid) {
        var frameGrid = [];
        var w = normalGrid[0].length;
        var h = normalGrid.length;
        for (var x = 0 ; x < w ; x++) {
          frameGrid[x] = [];
          for (var y = 0 ; y < h ; y++) {
            frameGrid[x][y] = normalGrid[y][x];
          }
        }
        return frameGrid;
      }
    };
  })();
  