• Jump To … +
    analyze.coffee autoload.coffee blender.coffee calculate.coffee caman.coffee convert.coffee event.coffee filter.coffee io.coffee layer.coffee logger.coffee module.coffee pixel.coffee plugin.coffee renderer.coffee store.coffee util.coffee blenders.coffee filters.coffee size.coffee blur.coffee camera.coffee compoundBlur.coffee edges.coffee posterize.coffee presets.coffee rotate.coffee stackBlur.coffee threshold.coffee
  • renderer.coffee

  • ¶

    Handles all of the various rendering methods in Caman. Most of the image modification happens here. A new Renderer object is created for every render operation.

    class Caman.Renderer
  • ¶

    The number of blocks to split the image into during the render process to simulate concurrency. This also helps the browser manage the (possibly) long running render jobs.

      @Blocks = if Caman.NodeJS then require('os').cpus().length else 4
    
      constructor: (@c) ->
        @renderQueue = []
        @modPixelData = null
    
      add: (job) ->
        return unless job?
        @renderQueue.push job
  • ¶

    Grabs the next operation from the render queue and passes it to Renderer for execution

      processNext: =>
  • ¶

    If the queue is empty, fire the finished callback

        if @renderQueue.length is 0
          Event.trigger @, "renderFinished"
          @finishedFn.call(@c) if @finishedFn?
    
          return @
    
        @currentJob = @renderQueue.shift()
    
        switch @currentJob.type
          when Filter.Type.LayerDequeue
            layer = @c.canvasQueue.shift()
            @c.executeLayer layer
            @processNext()
          when Filter.Type.LayerFinished
            @c.applyCurrentLayer()
            @c.popContext()
            @processNext()
          when Filter.Type.LoadOverlay
            @loadOverlay @currentJob.layer, @currentJob.src
          when Filter.Type.Plugin
            @executePlugin()
          else
            @executeFilter()
    
      execute: (callback) ->
        @finishedFn = callback
        @modPixelData = Util.dataArray(@c.pixelData.length)
    
        @processNext()
    
      eachBlock: (fn) ->
  • ¶

    Prepare all the required render data

        @blocksDone = 0
    
        n = @c.pixelData.length
        blockPixelLength = Math.floor (n / 4) / Renderer.Blocks
        blockN = blockPixelLength * 4
        lastBlockN = blockN + ((n / 4) % Renderer.Blocks) * 4
    
        for i in [0...Renderer.Blocks]
          start = i * blockN
          end = start + (if i is Renderer.Blocks - 1 then lastBlockN else blockN)
    
          if Caman.NodeJS
            f = Fiber => fn.call(@, i, start, end)
            bnum = f.run()
            @blockFinished(bnum)
          else
            setTimeout do (i, start, end) =>
              => fn.call(@, i, start, end)
            , 0
  • ¶

    The core of the image rendering, this function executes the provided filter.

    NOTE: this does not write the updated pixel data to the canvas. That happens when all filters are finished rendering in order to be as fast as possible.

      executeFilter: ->
        Event.trigger @c, "processStart", @currentJob
    
        if @currentJob.type is Filter.Type.Single
          @eachBlock @renderBlock
        else
          @eachBlock @renderKernel
  • ¶

    Executes a standalone plugin

      executePlugin: ->
        Log.debug "Executing plugin #{@currentJob.plugin}"
        Plugin.execute @c, @currentJob.plugin, @currentJob.args
        Log.debug "Plugin #{@currentJob.plugin} finished!"
    
        @processNext()
  • ¶

    Renders a single block of the canvas with the current filter function

      renderBlock: (bnum, start, end) ->
        Log.debug "Block ##{bnum} - Filter: #{@currentJob.name}, Start: #{start}, End: #{end}"
        Event.trigger @c, "blockStarted",
          blockNum: bnum
          totalBlocks: Renderer.Blocks
          startPixel: start
          endPixel: end
    
        pixel = new Pixel()
        pixel.setContext @c
    
        for i in [start...end] by 4
          pixel.loc = i
    
          pixel.r = @c.pixelData[i]
          pixel.g = @c.pixelData[i+1]
          pixel.b = @c.pixelData[i+2]
          pixel.a = @c.pixelData[i+3]
    
          @currentJob.processFn pixel
    
          @c.pixelData[i]   = Util.clampRGB pixel.r
          @c.pixelData[i+1] = Util.clampRGB pixel.g
          @c.pixelData[i+2] = Util.clampRGB pixel.b
          @c.pixelData[i+3] = Util.clampRGB pixel.a
    
        if Caman.NodeJS
          Fiber.yield(bnum)
        else
          @blockFinished bnum
  • ¶

    Applies an image kernel to the canvas

      renderKernel: (bnum, start, end) ->
        name = @currentJob.name
        bias = @currentJob.bias
        divisor = @currentJob.divisor
        n = @c.pixelData.length
    
        adjust = @currentJob.adjust
        adjustSize = Math.sqrt adjust.length
    
        kernel = []
    
        Log.debug "Rendering kernel - Filter: #{@currentJob.name}"
    
        start = Math.max start, @c.dimensions.width * 4 * ((adjustSize - 1) / 2)
        end = Math.min end, n - (@c.dimensions.width * 4 * ((adjustSize - 1) / 2))
    
        builder = (adjustSize - 1) / 2
    
        pixel = new Pixel()
        pixel.setContext(@c)
    
        for i in [start...end] by 4
          pixel.loc = i
          builderIndex = 0
    
          for j in [-builder..builder]
            for k in [builder..-builder]
              p = pixel.getPixelRelative j, k
              kernel[builderIndex * 3]     = p.r
              kernel[builderIndex * 3 + 1] = p.g
              kernel[builderIndex * 3 + 2] = p.b
    
              builderIndex++
    
          res = @processKernel adjust, kernel, divisor, bias
    
          @modPixelData[i]    = Util.clampRGB(res.r)
          @modPixelData[i+1]  = Util.clampRGB(res.g)
          @modPixelData[i+2]  = Util.clampRGB(res.b)
          @modPixelData[i+3]  = @c.pixelData[i+3]
    
        if Caman.NodeJS
          Fiber.yield(bnum)
        else
          @blockFinished bnum
  • ¶

    Called when a single block is finished rendering. Once all blocks are done, we signal that this filter is finished rendering and continue to the next step.

      blockFinished: (bnum) ->
        Log.debug "Block ##{bnum} finished! Filter: #{@currentJob.name}" if bnum >= 0
        @blocksDone++
    
        Event.trigger @c, "blockFinished",
          blockNum: bnum
          blocksFinished: @blocksDone
          totalBlocks: Renderer.Blocks
    
        if @blocksDone is Renderer.Blocks
          if @currentJob.type is Filter.Type.Kernel
            for i in [0...@c.pixelData.length]
              @c.pixelData[i] = @modPixelData[i]
    
          Log.debug "Filter #{@currentJob.name} finished!" if bnum >=0
          Event.trigger @c, "processComplete", @currentJob
    
          @processNext()
  • ¶

    The "filter function" for kernel adjustments.

      processKernel: (adjust, kernel, divisor, bias) ->
        val = r: 0, g: 0, b: 0
    
        for i in [0...adjust.length]
          val.r += adjust[i] * kernel[i * 3]
          val.g += adjust[i] * kernel[i * 3 + 1]
          val.b += adjust[i] * kernel[i * 3 + 2]
    
        val.r = (val.r / divisor) + bias
        val.g = (val.g / divisor) + bias
        val.b = (val.b / divisor) + bias
        val
  • ¶

    Loads an image onto the current canvas

      loadOverlay: (layer, src) ->
        img = document.createElement 'img'
        img.onload = =>
          layer.context.drawImage img, 0, 0, @c.dimensions.width, @c.dimensions.height
          layer.imageData = layer.context.getImageData 0, 0, @c.dimensions.width, @c.dimensions.height
          layer.pixelData = layer.imageData.data
    
          @c.pixelData = layer.pixelData
    
          @processNext()
    
        proxyUrl = IO.remoteCheck src
        img.src = if proxyUrl? then proxyUrl else src
    
    Renderer = Caman.Renderer