SlideShare a Scribd company logo
1 of 44
The Canvas API
 for Rubyists
  Cascadia Ruby, August 3rd, 2012
       Harry Dean Hudson
Who Am I?




 Dean Hudson, @deanero
Bit Twiddler at Massively Fun
We make games with
open web technology.
  (And are hiring).

http://massivelyfun.com
    @massivelyfun
Word * Word
What is this talk?
• Actually less about Canvas API and more
   about patterns in which it can be used...
• No Ruby! (Sorry Rubyists).
• Play along! https://github.com/massivelyfun/
   canvas-playground
• ...in 5 / 7 / 5.
Canvas API:
2D graphics in browser.
   It is quite simple.
Canvas is
• 2D, immediate mode, graphics API
• Well supported in modern browsers
• A collection of drawing calls
• Not Flash!
(function () {

    var ImageData = function (width, height) {

         this.width     = width   || 10;

         this.height = height || 10;

         numPixels = this.width * this.height;

         this.data = new Uint8Array(numPixels * 4);

    };



    var CanvasGradient = function () {};



    CanvasGradient.prototype.addColorStop = function (offset, color) {

         return undefined;

    };



    var CanvasPattern = function (image, repetitionStyle) {

         this.image = image;

         this.repetitionStyle = repetitionStyle;

    };



    var TextMetrics = function (ctx, text) {




                                           this...
         // quick and dirty style

         var fontSize = parseInt(ctx.font),

             chars = text.split().length;

         this.width = fontSize * chars;

    }



    var CanvasRenderingContext2D = function (canvas) {

         this.canvas                         = canvas;

         this.fillStyle                      = "rgb(0,0,0)";

         this.font                           = "10px sans-serif";

         this.globalAlpha                    = 1.0;

         this.globalCompositionOperation = "source-over";

         this.lineCap                        = "butt";

         this.lineJoin                       = "miter";

         this.lineWidth                      = 1.0;

         this.miterLimit                     = 10;

         this.textAlign                      = "start";

         this.textBaseLine                   = "alphabetic";

         this.shadowBlur                     = 0;

         this.shadowColor                    = "rgba(0,0,0,0)";

         this.shadowOffsetX                  = 0;

         this.shadowOffsetY                  = 0;

         this.strokeStyle                    = "rgb(0,0,0)";



         this.__width              = this.canvas.width;

         this.__height             = this.canvas.height;

         this.__imageData          = null; // don't do this until we need it, it's a memory hog.

                                           // new ImageData(this.__width, this.__height);

         this.__curX               = 0;

         this.__curY               = 0;
this.__openSubpath      = true;

     this.__initTime         = new Date();

     this.__lastUpdateTime = null;

     this.__lastFillTime     = null;

     this.__updateCount      = 0;

     this.__fillCount        = 0;

};



CanvasRenderingContext2D.prototype.__update = function () {

     var args = Array.prototype.slice.call(arguments);

     this.__lastUpdateTime = new Date();

     this.__updateCount++;

}

CanvasRenderingContext2D.prototype.__fill = function () {

     var args = Array.prototype.slice.call(arguments);

     this.__lastFillTime = new Date();

     this.__fillCount++;

}



// Stub out the real methods. I'm explicitly returning undefined

// in cases where the API calls for void return, so as to be clear

// about the intent. This is a simple sub-set of the API focused

// on operations for images. TODO: implement transforms and state.

CanvasRenderingContext2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) {

     this.__openSubpath      = true;




...plus this...
     return undefined;

};

CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) {

     this.__openSubpath      = true;

     this.__curX = x2;

     this.__curY = y2;

     return undefined;

};

CanvasRenderingContext2D.prototype.beginPath = function () {

     this.__openSubpath = true;

     return undefined;

};

CanvasRenderingContext2D.prototype.bezierCurveTo = function (cpX1, cpY1, cpX2, cpY2, x, y) {

     this.__openSubpath      = true;

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.clearRect = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.clip = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.closePath = function () {

     this.__openSubpath = false;

     return undefined;

};

CanvasRenderingContext2D.prototype.createImageData = function () {

     var args = Array.prototype.slice.call(arguments);
CanvasRenderingContext2D.prototype.clip = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.closePath = function () {

     this.__openSubpath = false;

     return undefined;

};

CanvasRenderingContext2D.prototype.createImageData = function () {

     var args = Array.prototype.slice.call(arguments);



     if (args[0].hasOwnProperty('data') && typeof args[0].data !== 'undefined') {

         return new ImageData(args[0].data.length, 1);

     } else if (typeof args[0] === 'number' && typeof args[1] === 'number') {

         return new ImageData(args[0], args[1]);

     } else {

         throw new Error("Invalid arguments. createImageData() takes 1 or 2 args.");

     }

};

CanvasRenderingContext2D.prototype.createLinearGradient = function (xStart, yStart, xEnd, yEnd) {

     return new CanvasGradient();

};

CanvasRenderingContext2D.prototype.createPattern = function (image, repetitionStyle) {

     return new CanvasPattern(image, repetitionStyle);

};

CanvasRenderingContext2D.prototype.createRadialGradient = function (xStart, yStart, radiusStart, xEnd, yEnd, radiusEnd) {




...plus this...
     return new CanvasGradient();

};

CanvasRenderingContext2D.prototype.drawImage = function () {

     switch(arguments.length) {

     case 3:

         return CanvasRenderingContext2D.prototype.__drawImage3.apply(this, arguments);

     case 5:

         return CanvasRenderingContext2D.prototype.__drawImage5.apply(this, arguments);

     case 9:

         return CanvasRenderingContext2D.prototype.__drawImage9.apply(this, arguments);

     default:

         throw new Error("Invalid number of arguments. drawImage() takes, 3, 5, or 9 args.");

     }

};



// All that contortion and I don't do anything with it. I'm stubbing

// this out in case I want to at some point.

CanvasRenderingContext2D.prototype.__drawImage3 = function (image, dx, dy) {

     return undefined;

};

CanvasRenderingContext2D.prototype.__drawImage5 = function (image, dx, dy, dw, dh) {

     return undefined;

};

CanvasRenderingContext2D.prototype.__drawImage9 = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {

     return undefined;

};

CanvasRenderingContext2D.prototype.fill = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.fillRect = function (x, y, width, height) {
return retImageData;

};

CanvasRenderingContext2D.prototype.isPointPath = function (x, y) {

     return true;

};

CanvasRenderingContext2D.prototype.lineTo = function (x, y) {

     this.__openSubpath      = true;

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.measureText = function (text) {

     return new TextMetrics(this, text);

};

CanvasRenderingContext2D.prototype.moveTo = function (x, y) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.putImageData = function (insertData, dx, dy, sx, sy, sw, sh) {

     if (arguments.length !== 7)

         throw new Error("putImageData requires 7 arguments")



     var imageData = this.__imageData || new ImageData(this.__width,   this.__height),




...plus this...
         startAt     = dx * dy * 4 + dx + 4,

         fromData,

         fromOffset = sx * sy * 4 + sx * 4,

         fromNumPixels = sw * sh * 4 + sw * 4,

         endAt = imageData.length - 1,

         howMany;



     if (typeof fromOffset === 'number' && typeof fromNumPixels === 'number') {

         fromData = insertData.data.slice(fromOffset, fromOffset + fromNumPixels);

     } else {

         fromData = insertData.data;

     }



     startAt + fromData.length > endAt ? howMany = endAt - startAt : howMany = startAt + fromData.length;

     imageData.data.splice(startAt, howMany, fromData);



     return undefined;

};

CanvasRenderingContext2D.prototype.quadraticCurveTo = function (cpX, cpY, x, y) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.rect = function (x, y, width, height) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.restore = function () {

     return undefined;
return undefined;

    };

    CanvasRenderingContext2D.prototype.restore = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.rotate = function (angle) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.save = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.scale = function (sx, sy) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.setTransform = function (a, b, c, d, e, f) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.stroke = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.strokeRect = function (x, y, width, height) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.strokeText = function (text, x, y, max) {

         return undefined;




           ...and this.
    };

    CanvasRenderingContext2D.prototype.transform = function (a, b, c, d, e, f) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.translate = function (dx, dy) {

         return undefined;

    };



    var Canvas = function () {

         this.width   = 10; // API default is 300 x 150, but that makes

         this.height = 10; // our ImageData a memory hog.

    };



    Canvas.prototype.getContext = function (cxtType) {

         return new CanvasRenderingContext2D(this);

    };



    Canvas.prototype.toDataURL = function () {

         var buf = new Buffer("Say hello to my little friend.");

         return "data:text/plain;base64," + buf.toString('base64');

    };



    module.exports = Canvas;

})();
The CanvasContext:
It has all the calls to draw
  Pixels to the screen.
Simple, no?
cvs = document.getElementById("canvas")
ctx = cvs.getContext("2D")

# ctx has all your draw methods...

ctx.fillText("Hello!", 100, 100, 200)
Simple, no?
cvs = document.getElementById("canvas")
ctx = cvs.getContext("2D")

# ctx has all your draw methods...

ctx.fillText("Hello!", 100, 100, 200)

     You’ll only see this from here out.
dean@dean:~$ grep prototype canvas.js |
                 wc -l
                   38

  You can learn the draw
    API in a weekend!
dean@dean:~$ grep prototype canvas.js |
                 wc -l
                   38

  You can learn the draw
     API in a weekend!
...so, on to bigger things!
Browser event loops
Are not well suited for games
   You must roll your own
Simple Game Loop
 • Handle queued UI/server events
 • Update state of “things” on screen.
 • Re-draw!
# a fake game loop
gameLoop =
  run: ->
    @handleQueuedEvents()
    @update()
    @draw()
    # loop somehow?
setInterval()?
setTimeout()?
NO!
16 ms !=
16.666...ms
    Text

 (60 FPS)
For accurate loops
requestAnimationFrame()
 Will bring you great joy
requestAnimationFrame
 allows the browser to
manage screen updates
      efficiently...
requestAnimationFrame
 allows the browser to
manage screen updates
      efficiently...

 ...but is not uniformly
        supported.
buildRAFPolyfill = ->
  requestAnimationFrame =
    requestAnimationFrame         ||
    webkitRequestAnimationFrame   ||
    mozRequestAnimationFrame      ||
    oRequestAnimationFrame        ||
    msRequestAnimationFrame       ||
    -> (cb, elt)
      setTimeout (cb) ->
        cb(+new Date())
      , 1000 / 60
class GameLoop
  constructor: (cvs) ->
    @ctx       = cvs.getContext("2D")
    @entities = []

  addEntity: (entity) ->
    @entities.push(entity)

  run: () =>
    tick = @frameId = requestAnimationFrame(@run)

    # Update entities
    entity.update(tick) for entity in @entities

    # Draw!
    entity.draw(@ctx) for entity in @entities

  stop: ->
    root.cancelAnimationFrame(@frameId)
class GameLoop
  constructor: (cvs) ->
    @ctx       = cvs.getContext("2D")
    @entities = []

  addEntity: (entity) ->
    @entities.push(entity)

  run: () =>
    tick = @frameId = requestAnimationFrame(@run)

    # Update entities
    entity.update(tick) for entity in @entities

    # Draw!
    entity.draw(@ctx) for entity in @entities

  stop: ->
    root.cancelAnimationFrame(@frameId)

requestAnimationFrame returns a int “id”
Entities and Rects:
The largest things are built
 From just these pieces.
Simple Game
   Entity
• Respond to update call
• Manage position, height, width
• Delegate draw calls
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity

         Game level logic, once per tick
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity

               DELEGATE!!!!!
Simple Draw
        Primitive
• Associated with Entity
• Responds to draw() call
• Has actual CanvasContext2D drawing calls
• You can determine containment here
  (image hit detection, for instance)
#
# Rect: Drawing primitive for canvas.

class Rect
  constructor: (options = {}) ->
    @width    = options?.width ? 1
    @height   = options?.height ? 1

    {x, y} = options

    @position = {x: x, y: y}

  draw: (canvasCtx) ->
    throw new Error("Implement draw()")

module.exports = Rect
#
# Rect: Drawing primitive for canvas.

class Rect
  constructor: (options = {}) ->
    @width    = options?.width ? 1
    @height   = options?.height ? 1

    {x, y} = options

    @position = {x: x, y: y}

  draw: (canvasCtx) ->
    throw new Error("Implement draw()")

module.exports = Rect

      Your *simple* interface
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box


                Implement draw
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box


      Do work on CanvasContext2D
Canvas tests are hard;
The one true way is to cheat.
Stub, Stub, stub the world.
CanvasRenderingContext2D.prototype.__update = function () {
    var args = Array.prototype.slice.call(arguments);
    this.__lastUpdateTime = new Date();
    this.__updateCount++;
}
CanvasRenderingContext2D.prototype.__fill = function () {
    var args = Array.prototype.slice.call(arguments);
    this.__lastFillTime = new Date();
    this.__fillCount++;
}

//   Stub out the real methods. I'm explicitly returning undefined
//   in cases where the API calls for void return, so as to be clear
//   about the intent. This is a simple sub-set of the API focused
//   on operations for images. TODO: implement transforms and state.

CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) {
    this.__openSubpath    = true;
    this.__curX = x2;
    this.__curY = y2;
    return undefined;



                               ...etc.
};
Questions?
Thanks!




dean@massivelyfun.com, @deanero
     http://massivelyfun.com

More Related Content

What's hot

Exploring Canvas
Exploring CanvasExploring Canvas
Exploring Canvas
Kevin Hoyt
 
openFrameworks 007 - graphics
openFrameworks 007 - graphicsopenFrameworks 007 - graphics
openFrameworks 007 - graphics
roxlu
 
Cursor implementation
Cursor implementationCursor implementation
Cursor implementation
vicky201
 

What's hot (20)

openFrameworks 007 - GL
openFrameworks 007 - GL openFrameworks 007 - GL
openFrameworks 007 - GL
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic LabsTypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
 
Hidden Gems in Swift
Hidden Gems in SwiftHidden Gems in Swift
Hidden Gems in Swift
 
JavaScript Design Patterns
JavaScript Design PatternsJavaScript Design Patterns
JavaScript Design Patterns
 
Css5 canvas
Css5 canvasCss5 canvas
Css5 canvas
 
C# v8 new features - raimundas banevicius
C# v8 new features - raimundas baneviciusC# v8 new features - raimundas banevicius
C# v8 new features - raimundas banevicius
 
Encoder + decoder
Encoder + decoderEncoder + decoder
Encoder + decoder
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring Canvas
 
WebGL 2.0 Reference Guide
WebGL 2.0 Reference GuideWebGL 2.0 Reference Guide
WebGL 2.0 Reference Guide
 
Oxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resourcesOxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resources
 
Composite Pattern
Composite PatternComposite Pattern
Composite Pattern
 
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
 
Js hacks
Js hacksJs hacks
Js hacks
 
openFrameworks 007 - graphics
openFrameworks 007 - graphicsopenFrameworks 007 - graphics
openFrameworks 007 - graphics
 
JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)
 
Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Functional microscope - Lenses in C++
Functional microscope - Lenses in C++
 
Cursor implementation
Cursor implementationCursor implementation
Cursor implementation
 
Say It With Javascript
Say It With JavascriptSay It With Javascript
Say It With Javascript
 
Pointers
PointersPointers
Pointers
 
P1
P1P1
P1
 

Similar to The Canvas API for Rubyists

javascript Model- Render & canvas sample
javascript Model- Render & canvas samplejavascript Model- Render & canvas sample
javascript Model- Render & canvas sample
Hika Maeng
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montage
Kris Kowal
 
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficientTh 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Bin Shao
 
Player x 0 y ga.docx
Player x 0 y ga.docxPlayer x 0 y ga.docx
Player x 0 y ga.docx
mattjtoni51554
 
Webgl para JavaScripters
Webgl para JavaScriptersWebgl para JavaScripters
Webgl para JavaScripters
gerbille
 
need help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdfneed help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdf
arcotstarsports
 

Similar to The Canvas API for Rubyists (20)

javascript Model- Render & canvas sample
javascript Model- Render & canvas samplejavascript Model- Render & canvas sample
javascript Model- Render & canvas sample
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montage
 
Object-Oriented JavaScript
Object-Oriented JavaScriptObject-Oriented JavaScript
Object-Oriented JavaScript
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascript
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
ES6 Overview
ES6 OverviewES6 Overview
ES6 Overview
 
ES6 in Real Life
ES6 in Real LifeES6 in Real Life
ES6 in Real Life
 
05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developer05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developer
 
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficientTh 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
 
Player x 0 y ga.docx
Player x 0 y ga.docxPlayer x 0 y ga.docx
Player x 0 y ga.docx
 
Jeroen Vloothuis Bend Kss To Your Will
Jeroen Vloothuis   Bend Kss To Your WillJeroen Vloothuis   Bend Kss To Your Will
Jeroen Vloothuis Bend Kss To Your Will
 
Webgl para JavaScripters
Webgl para JavaScriptersWebgl para JavaScripters
Webgl para JavaScripters
 
need help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdfneed help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdf
 
Using Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data VisualisationUsing Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data Visualisation
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
Sencha Touch
Sencha TouchSencha Touch
Sencha Touch
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013
 
Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015
 

More from deanhudson (6)

Seattle.rb 6.4
Seattle.rb 6.4Seattle.rb 6.4
Seattle.rb 6.4
 
Mcdm presentations
Mcdm presentationsMcdm presentations
Mcdm presentations
 
Stupid Canvas Tricks
Stupid Canvas TricksStupid Canvas Tricks
Stupid Canvas Tricks
 
Pointer Events in Canvas
Pointer Events in CanvasPointer Events in Canvas
Pointer Events in Canvas
 
Com546 Final Pres
Com546 Final PresCom546 Final Pres
Com546 Final Pres
 
Reading Slides
Reading SlidesReading Slides
Reading Slides
 

Recently uploaded

Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Recently uploaded (20)

How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Evaluating the top large language models.pdf
Evaluating the top large language models.pdfEvaluating the top large language models.pdf
Evaluating the top large language models.pdf
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 

The Canvas API for Rubyists

  • 1. The Canvas API for Rubyists Cascadia Ruby, August 3rd, 2012 Harry Dean Hudson
  • 2. Who Am I? Dean Hudson, @deanero Bit Twiddler at Massively Fun
  • 3. We make games with open web technology. (And are hiring). http://massivelyfun.com @massivelyfun
  • 5. What is this talk? • Actually less about Canvas API and more about patterns in which it can be used... • No Ruby! (Sorry Rubyists). • Play along! https://github.com/massivelyfun/ canvas-playground • ...in 5 / 7 / 5.
  • 6. Canvas API: 2D graphics in browser. It is quite simple.
  • 7. Canvas is • 2D, immediate mode, graphics API • Well supported in modern browsers • A collection of drawing calls • Not Flash!
  • 8. (function () { var ImageData = function (width, height) { this.width = width || 10; this.height = height || 10; numPixels = this.width * this.height; this.data = new Uint8Array(numPixels * 4); }; var CanvasGradient = function () {}; CanvasGradient.prototype.addColorStop = function (offset, color) { return undefined; }; var CanvasPattern = function (image, repetitionStyle) { this.image = image; this.repetitionStyle = repetitionStyle; }; var TextMetrics = function (ctx, text) { this... // quick and dirty style var fontSize = parseInt(ctx.font), chars = text.split().length; this.width = fontSize * chars; } var CanvasRenderingContext2D = function (canvas) { this.canvas = canvas; this.fillStyle = "rgb(0,0,0)"; this.font = "10px sans-serif"; this.globalAlpha = 1.0; this.globalCompositionOperation = "source-over"; this.lineCap = "butt"; this.lineJoin = "miter"; this.lineWidth = 1.0; this.miterLimit = 10; this.textAlign = "start"; this.textBaseLine = "alphabetic"; this.shadowBlur = 0; this.shadowColor = "rgba(0,0,0,0)"; this.shadowOffsetX = 0; this.shadowOffsetY = 0; this.strokeStyle = "rgb(0,0,0)"; this.__width = this.canvas.width; this.__height = this.canvas.height; this.__imageData = null; // don't do this until we need it, it's a memory hog. // new ImageData(this.__width, this.__height); this.__curX = 0; this.__curY = 0;
  • 9. this.__openSubpath = true; this.__initTime = new Date(); this.__lastUpdateTime = null; this.__lastFillTime = null; this.__updateCount = 0; this.__fillCount = 0; }; CanvasRenderingContext2D.prototype.__update = function () { var args = Array.prototype.slice.call(arguments); this.__lastUpdateTime = new Date(); this.__updateCount++; } CanvasRenderingContext2D.prototype.__fill = function () { var args = Array.prototype.slice.call(arguments); this.__lastFillTime = new Date(); this.__fillCount++; } // Stub out the real methods. I'm explicitly returning undefined // in cases where the API calls for void return, so as to be clear // about the intent. This is a simple sub-set of the API focused // on operations for images. TODO: implement transforms and state. CanvasRenderingContext2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { this.__openSubpath = true; ...plus this... return undefined; }; CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { this.__openSubpath = true; this.__curX = x2; this.__curY = y2; return undefined; }; CanvasRenderingContext2D.prototype.beginPath = function () { this.__openSubpath = true; return undefined; }; CanvasRenderingContext2D.prototype.bezierCurveTo = function (cpX1, cpY1, cpX2, cpY2, x, y) { this.__openSubpath = true; this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.clearRect = function () { return undefined; }; CanvasRenderingContext2D.prototype.clip = function () { return undefined; }; CanvasRenderingContext2D.prototype.closePath = function () { this.__openSubpath = false; return undefined; }; CanvasRenderingContext2D.prototype.createImageData = function () { var args = Array.prototype.slice.call(arguments);
  • 10. CanvasRenderingContext2D.prototype.clip = function () { return undefined; }; CanvasRenderingContext2D.prototype.closePath = function () { this.__openSubpath = false; return undefined; }; CanvasRenderingContext2D.prototype.createImageData = function () { var args = Array.prototype.slice.call(arguments); if (args[0].hasOwnProperty('data') && typeof args[0].data !== 'undefined') { return new ImageData(args[0].data.length, 1); } else if (typeof args[0] === 'number' && typeof args[1] === 'number') { return new ImageData(args[0], args[1]); } else { throw new Error("Invalid arguments. createImageData() takes 1 or 2 args."); } }; CanvasRenderingContext2D.prototype.createLinearGradient = function (xStart, yStart, xEnd, yEnd) { return new CanvasGradient(); }; CanvasRenderingContext2D.prototype.createPattern = function (image, repetitionStyle) { return new CanvasPattern(image, repetitionStyle); }; CanvasRenderingContext2D.prototype.createRadialGradient = function (xStart, yStart, radiusStart, xEnd, yEnd, radiusEnd) { ...plus this... return new CanvasGradient(); }; CanvasRenderingContext2D.prototype.drawImage = function () { switch(arguments.length) { case 3: return CanvasRenderingContext2D.prototype.__drawImage3.apply(this, arguments); case 5: return CanvasRenderingContext2D.prototype.__drawImage5.apply(this, arguments); case 9: return CanvasRenderingContext2D.prototype.__drawImage9.apply(this, arguments); default: throw new Error("Invalid number of arguments. drawImage() takes, 3, 5, or 9 args."); } }; // All that contortion and I don't do anything with it. I'm stubbing // this out in case I want to at some point. CanvasRenderingContext2D.prototype.__drawImage3 = function (image, dx, dy) { return undefined; }; CanvasRenderingContext2D.prototype.__drawImage5 = function (image, dx, dy, dw, dh) { return undefined; }; CanvasRenderingContext2D.prototype.__drawImage9 = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { return undefined; }; CanvasRenderingContext2D.prototype.fill = function () { return undefined; }; CanvasRenderingContext2D.prototype.fillRect = function (x, y, width, height) {
  • 11. return retImageData; }; CanvasRenderingContext2D.prototype.isPointPath = function (x, y) { return true; }; CanvasRenderingContext2D.prototype.lineTo = function (x, y) { this.__openSubpath = true; this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.measureText = function (text) { return new TextMetrics(this, text); }; CanvasRenderingContext2D.prototype.moveTo = function (x, y) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.putImageData = function (insertData, dx, dy, sx, sy, sw, sh) { if (arguments.length !== 7) throw new Error("putImageData requires 7 arguments") var imageData = this.__imageData || new ImageData(this.__width, this.__height), ...plus this... startAt = dx * dy * 4 + dx + 4, fromData, fromOffset = sx * sy * 4 + sx * 4, fromNumPixels = sw * sh * 4 + sw * 4, endAt = imageData.length - 1, howMany; if (typeof fromOffset === 'number' && typeof fromNumPixels === 'number') { fromData = insertData.data.slice(fromOffset, fromOffset + fromNumPixels); } else { fromData = insertData.data; } startAt + fromData.length > endAt ? howMany = endAt - startAt : howMany = startAt + fromData.length; imageData.data.splice(startAt, howMany, fromData); return undefined; }; CanvasRenderingContext2D.prototype.quadraticCurveTo = function (cpX, cpY, x, y) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.rect = function (x, y, width, height) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.restore = function () { return undefined;
  • 12. return undefined; }; CanvasRenderingContext2D.prototype.restore = function () { return undefined; }; CanvasRenderingContext2D.prototype.rotate = function (angle) { return undefined; }; CanvasRenderingContext2D.prototype.save = function () { return undefined; }; CanvasRenderingContext2D.prototype.scale = function (sx, sy) { return undefined; }; CanvasRenderingContext2D.prototype.setTransform = function (a, b, c, d, e, f) { return undefined; }; CanvasRenderingContext2D.prototype.stroke = function () { return undefined; }; CanvasRenderingContext2D.prototype.strokeRect = function (x, y, width, height) { return undefined; }; CanvasRenderingContext2D.prototype.strokeText = function (text, x, y, max) { return undefined; ...and this. }; CanvasRenderingContext2D.prototype.transform = function (a, b, c, d, e, f) { return undefined; }; CanvasRenderingContext2D.prototype.translate = function (dx, dy) { return undefined; }; var Canvas = function () { this.width = 10; // API default is 300 x 150, but that makes this.height = 10; // our ImageData a memory hog. }; Canvas.prototype.getContext = function (cxtType) { return new CanvasRenderingContext2D(this); }; Canvas.prototype.toDataURL = function () { var buf = new Buffer("Say hello to my little friend."); return "data:text/plain;base64," + buf.toString('base64'); }; module.exports = Canvas; })();
  • 13. The CanvasContext: It has all the calls to draw Pixels to the screen.
  • 14. Simple, no? cvs = document.getElementById("canvas") ctx = cvs.getContext("2D") # ctx has all your draw methods... ctx.fillText("Hello!", 100, 100, 200)
  • 15. Simple, no? cvs = document.getElementById("canvas") ctx = cvs.getContext("2D") # ctx has all your draw methods... ctx.fillText("Hello!", 100, 100, 200) You’ll only see this from here out.
  • 16. dean@dean:~$ grep prototype canvas.js | wc -l 38 You can learn the draw API in a weekend!
  • 17. dean@dean:~$ grep prototype canvas.js | wc -l 38 You can learn the draw API in a weekend! ...so, on to bigger things!
  • 18. Browser event loops Are not well suited for games You must roll your own
  • 19. Simple Game Loop • Handle queued UI/server events • Update state of “things” on screen. • Re-draw!
  • 20. # a fake game loop gameLoop = run: -> @handleQueuedEvents() @update() @draw() # loop somehow?
  • 22. NO!
  • 23. 16 ms != 16.666...ms Text (60 FPS)
  • 24. For accurate loops requestAnimationFrame() Will bring you great joy
  • 25. requestAnimationFrame allows the browser to manage screen updates efficiently...
  • 26. requestAnimationFrame allows the browser to manage screen updates efficiently... ...but is not uniformly supported.
  • 27. buildRAFPolyfill = -> requestAnimationFrame = requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || oRequestAnimationFrame || msRequestAnimationFrame || -> (cb, elt) setTimeout (cb) -> cb(+new Date()) , 1000 / 60
  • 28. class GameLoop constructor: (cvs) -> @ctx = cvs.getContext("2D") @entities = [] addEntity: (entity) -> @entities.push(entity) run: () => tick = @frameId = requestAnimationFrame(@run) # Update entities entity.update(tick) for entity in @entities # Draw! entity.draw(@ctx) for entity in @entities stop: -> root.cancelAnimationFrame(@frameId)
  • 29. class GameLoop constructor: (cvs) -> @ctx = cvs.getContext("2D") @entities = [] addEntity: (entity) -> @entities.push(entity) run: () => tick = @frameId = requestAnimationFrame(@run) # Update entities entity.update(tick) for entity in @entities # Draw! entity.draw(@ctx) for entity in @entities stop: -> root.cancelAnimationFrame(@frameId) requestAnimationFrame returns a int “id”
  • 30. Entities and Rects: The largest things are built From just these pieces.
  • 31. Simple Game Entity • Respond to update call • Manage position, height, width • Delegate draw calls
  • 32. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity
  • 33. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity Game level logic, once per tick
  • 34. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity DELEGATE!!!!!
  • 35. Simple Draw Primitive • Associated with Entity • Responds to draw() call • Has actual CanvasContext2D drawing calls • You can determine containment here (image hit detection, for instance)
  • 36. # # Rect: Drawing primitive for canvas. class Rect constructor: (options = {}) -> @width = options?.width ? 1 @height = options?.height ? 1 {x, y} = options @position = {x: x, y: y} draw: (canvasCtx) -> throw new Error("Implement draw()") module.exports = Rect
  • 37. # # Rect: Drawing primitive for canvas. class Rect constructor: (options = {}) -> @width = options?.width ? 1 @height = options?.height ? 1 {x, y} = options @position = {x: x, y: y} draw: (canvasCtx) -> throw new Error("Implement draw()") module.exports = Rect Your *simple* interface
  • 38. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box
  • 39. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box Implement draw
  • 40. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box Do work on CanvasContext2D
  • 41. Canvas tests are hard; The one true way is to cheat. Stub, Stub, stub the world.
  • 42. CanvasRenderingContext2D.prototype.__update = function () { var args = Array.prototype.slice.call(arguments); this.__lastUpdateTime = new Date(); this.__updateCount++; } CanvasRenderingContext2D.prototype.__fill = function () { var args = Array.prototype.slice.call(arguments); this.__lastFillTime = new Date(); this.__fillCount++; } // Stub out the real methods. I'm explicitly returning undefined // in cases where the API calls for void return, so as to be clear // about the intent. This is a simple sub-set of the API focused // on operations for images. TODO: implement transforms and state. CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { this.__openSubpath = true; this.__curX = x2; this.__curY = y2; return undefined; ...etc. };
  • 44. Thanks! dean@massivelyfun.com, @deanero http://massivelyfun.com

Editor's Notes

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n