No JavaScript → no output and no interactivity.

Photo histogram in CoffeeKup — Smooth CoffeeScript

This literate program is interactive in its HTML5 form. Edit a CoffeeScript segment to try it. You can see the generated JavaScript as you modify a CoffeeScript function by typing ‘show name’ after its definition.

Photo histogram

It is easy to prototype an idea with CoffeeScript and CoffeeKup. This program shows separation of color channels as a starting point for some photo analysis.

HTML Rendering

In CoffeeKup you write a CoffeeScript function that is rendered to HTML. That function can contain the page elements, styling and CoffeeScript functions that are used on the web page. It can also refer to external files so you can modularize and separate the look and feel from the content. To load a script include a tag such as: script src: 'underscore.js'.

This histogram program can run embedded in a web page, where the program can be changed interactively or it can run standalone. Scripts are loaded into the window environment in a web browser, in the standalone environment global and require are used instead.

An existential test on exports can be used to determine which environment the program is running in — another way is to look at whether the window environment is available.

kup = if exports? then require 'coffeekup' else window.CoffeeKup

webapp = ->

User Interface

Since the program will use a canvas and some Unicode characters, its doctype and charset are HTML5 and UTF–8. The CSS styling for the application is — so far — very simple so it is included in-line. That is convenient when experimenting, if you are reading this on the web page then you can change the styling and immediately see the effect on the output.

  doctype 5
html ->
head ->
meta charset: 'utf-8'
title 'Histogram'
style 'body {color: #FFFFFF; background-color: #404040}
#background {position: absolute; top: 40px; left: 20px}
#image {position: absolute; top: 0px; left: 80px}
#picture {position: absolute; top: 0px; left: 80px}
#histogram {position: absolute; top: 140px; left: 0px}'

The img tag is used to load a photo normally from a file, however in the interactive environment it is instead loaded from a global predefined variable1. The application does not need to display the original picture, instead it shows a canvas with the processed picture. That canvas is therefore set to the same size and placed via absolute positioning right on top of it.

    body ->
div id: 'background', ->
img id: 'image', width: 90, height: 90, \
src: window?.ostrich ? '../img/ostrich.jpg'
canvas id: 'picture', width: 90, height: 90
canvas id: 'histogram', width: 256, height: 100, \
onClick: 'onChange()'

Functionality

The application responds to click events on the histogram by displaying either all color channels combined or one of the red, green, blue, or the transparency/alpha channel. First in the web application’s CoffeeScript section is a definition of constants and a variable view that holds an index of what is currently displayed.

    coffeescript ->
hues =
red: 'rgba(255, 128, 128, 0.5)'
green: 'rgba(128, 255, 128, 0.5)'
blue: 'rgba(128, 128, 255, 0.5)'
alpha: 'rgba(128, 128, 128, 0.5)'
legend = ['∀', 'R', 'G', 'B', 'α']
textColor = '#F7C762'
textFont = '12pt Times'
textPos = x:230, y:-80
view = 0

The data in a photo is stored according to the definition given in HTML Canvas 2D Context by W3C:

The CanvasPixelArray object provides ordered, indexed access to the color components of each pixel of the image data. The data must be represented in left-to-right order, row by row top to bottom, starting with the top left, with each pixel’s red, green, blue, and alpha components being given in that order for each pixel. Each component of each device pixel represented in this array must be in the range 0..255, representing the 8 bit value for that component. The components must be assigned consecutive indices starting with 0 for the top left pixel’s red component.

This standardized image format makes it easy to count the values in each of the channels with the for ... in statement’s index feature and the modulus % operator.

      analyze = (data) ->
bins = red: [], green: [], blue: [], alpha: []
for name, bin of bins
bin[i] = 0 for i in [0..255]
for val, i in data
switch i % 4
when 0 then bins.red[val]++
when 1 then bins.green[val]++
when 2 then bins.blue[val]++
when 3 then bins.alpha[val]++
bins

The width of the histogram canvas was chosen to be the same as the number of values in each of the color channel bins: 256. That simplifies the drawing of the plots, they only have to be scaled to match the height of the canvas. Since the y-coordinate on a canvas defaults to start at the top, the scale function is used to turn the coordinate system upside down — but only after the text legend has been printed in its predetermined location.

      drawGraphs = (ctx, graphs) ->
drawPlot = (ctx, plot, color) ->
ctx.fillStyle = color
ctx.beginPath()
ctx.moveTo 0, 0
for y, x in plot
ctx.lineTo x, y
ctx.lineTo plot.length, 0
ctx.closePath()
ctx.fill()

ctx.translate 0, ctx.canvas.height
ctx.fillStyle = textColor
ctx.font = textFont
ctx.fillText legend[view], textPos.x, textPos.y
ctx.scale 1, -1 # flip y-axis
drawPlot ctx, graphs.red, hues.red if view in [0, 1]
drawPlot ctx, graphs.green, hues.green if view in [0, 2]
drawPlot ctx, graphs.blue, hues.blue if view in [0, 3]
drawPlot ctx, graphs.alpha, hues.alpha if view in [0, 4]

Events

Shared variables, canvas contexts, and the display are initialized when the application has loaded.

      window.onload = ->
$ = (element) -> document.getElementById element
@image = $ 'image'
@canvas = $ 'picture'
@histogram = $ 'histogram'

@context = canvas.getContext '2d'
@plot = histogram.getContext '2d'
unless @context? or @plot?
alert 'No canvas in this browser.'
return
window.onChange()

A complete redraw is performed when a touch or click triggers an update. If it is an RGB channel that is being displayed then the other channels are set to zero so only the contribution from the current channel is shown in the photo. The view index is updated and clamped.

      window.onChange = ->
@histogram.width = @histogram.width # reset
@plot.clearRect 0, 0, @histogram.width, @histogram.height
@context.drawImage @image, 0, 0

picture = @context.getImageData 0, 0,
@image.width, @image.height
graphs = analyze picture.data
drawGraphs @plot, graphs, view

if 0 < view < 4
picture = @context.getImageData 0, 0,
@image.width, @image.height
for i in [0...picture.data.length] by 1
unless i%4 in [3, view-1] # unless alpha or current
picture.data[i] = 0
@context.putImageData picture, 0, 0

if view++ is 4 then view = 0
return

Wrap-up

This last bit do not relate to the web application but to the CoffeeKup rendering. The generated HTML is displayed either live in the interactive environment or as text — as nicely formatted as possible.

webpage = kup.render webapp, format:on
showDocument webpage

#show '\n' + _.escape webpage # Uncomment to show the html

 

Formats Standalone CoffeeScript Markdown PDF HTML

License Creative Commons Attribution Share Alike by autotelicum © 2554/2011


  1. This is a side effect of how the interactive environment is constructed . It uses an iframe with a URI encoded data attribute to display the embedded HTML output. The image is encoded in base64 and predefined to simplify the process. It is not something you would need to do when using CoffeeKup outside of this environment.