From 12e744e48098de82c1d400401db7a869b989dd10 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:12:23 -0500 Subject: [PATCH 01/22] =?UTF-8?q?Primera=20secci=C3=B3n=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 1520 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1520 insertions(+) create mode 100644 17_canvas.md diff --git a/17_canvas.md b/17_canvas.md new file mode 100644 index 000000000..024ef9ba5 --- /dev/null +++ b/17_canvas.md @@ -0,0 +1,1520 @@ +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} + +# Dibujando en Canvas + +{{quote {author: "M.C. Escher", title: "citado por Bruno Ernst en The Magic Mirror of M.C. Escher", chapter: true} + +El dibujo es decepción. + +quote}} + +{{index "Escher, M.C."}} + +{{figure {url: "img/chapter_picture_17.jpg", alt: "Imagen de un brazo robótico dibujando en un papel", chapter: "framed"}}} + +{{index CSS, "transform (CSS)", [DOM, graphics]}} + +Los navegadores nos proporcionan varias formas de mostrar((gráficos)). La más simple +es usar estilos para posiciones y colores de elementos regulares del DOM. +Esto puede ser tardado, como vimos en el [previous +chapter](juego) pasado. Agregando ((imagenes))s de fondo transparentes + a los nodos, podemos hacer que se vean exactamente de la form que +queremos. incluso es posible rotar o distorsionar nodos con el estilo `transform`. + +Pero estaríamos usando el DOM para algo para lo que +no fue diseñado. Algunas tareas, como dibujar una ((línea)) entre +puntos arbitrarios, son extremadamente incómodas de hacer con elementos HTML +regulares. + +{{index SVG, "img (HTML tag)"}} + +Tenemos dos alternativas. La primera es basada en el DOM, pero utiliza +_Scalable Vector Graphics_ (SVG), más que HTML. Piensa en SVG como un +dialecto de un ((documento)) de marcado que se centra en ((figura))s más que en +texto. Puedes embeber un documento SVG directamente en un archivo HTML o +puedes incluirlo con una etiqueta ``. + +{{index clearing, [DOM graphics], [interface, canvas]}} + +La segunda alternativa es llamada _((canvas))_. Un canvas es un +elemento del DOM que encapsula una ((imagen)). Proporciona una +intefaz de programción para dibujar ((forma))s en el espacio +del nodo. La principal diferencia entre un canvas y una imagen +SVG es que en SVG la descripción original de las figuras es +preservada de manera que puedan ser movidas o reescaladas en cualquier momento. Un canvas, +por otro lado, convierte las figuras a ((pixele))s (puntos de color en +una rejilla) tan pronto son dibujadas y no recuerda cuáles +pixeles representa. La única forma de mover una figura en un canvas es limpíando +el canvas (o la parte del canvas alrededor de la figura) y redibujarlo +con la figura en una nueva posición. + +## SVG + +This book will not go into ((SVG)) in detail, but I will briefly +explain how it works. At the [end of the +chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs +that you must consider when deciding which ((drawing)) mechanism is +appropriate for a given application. + +This is an HTML document with a simple SVG ((picture)) in it: + +```{lang: "text/html", sandbox: "svg"} +

Normal HTML here.

+ + + + +``` + +{{index "circle (SVG tag)", "rect (SVG tag)", "XML namespace", XML, "xmlns attribute"}} + +The `xmlns` attribute changes an element (and its children) to a +different _XML namespace_. This namespace, identified by a ((URL)), +specifies the dialect that we are currently speaking. The `` +and `` tags, which do not exist in HTML, do have a meaning in +SVG—they draw shapes using the style and position specified by their +attributes. + +{{if book + +The document is displayed like this: + +{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} + +if}} + +{{index [DOM, graphics]}} + +These tags create DOM elements, just like HTML tags, that +scripts can interact with. For example, this changes the `` +element to be ((color))ed cyan instead: + +```{sandbox: "svg"} +let circle = document.querySelector("circle"); +circle.setAttribute("fill", "cyan"); +``` + +## The canvas element + +{{index [canvas, size], "canvas (HTML tag)"}} + +Canvas ((graphics)) can be drawn onto a `` element. You can +give such an element `width` and `height` attributes to determine its +size in ((pixel))s. + +A new canvas is empty, meaning it is entirely ((transparent)) and thus +shows up as empty space in the document. + +{{index "2d (canvas context)", "webgl (canvas context)", OpenGL, [canvas, context], dimensions, [interface, canvas]}} + +The `` tag is intended to allow different styles of +((drawing)). To get access to an actual drawing interface, we +first need to create a _((context))_, an object whose methods provide +the drawing interface. There are currently two widely supported +drawing styles: `"2d"` for two-dimensional graphics and `"webgl"` for +three-dimensional graphics through the OpenGL interface. + +{{index rendering, graphics, efficiency}} + +This book won't discuss WebGL—we'll stick to two dimensions. But if +you are interested in three-dimensional graphics, I do encourage you +to look into WebGL. It provides a direct interface to graphics +hardware and allows you to render even complicated scenes efficiently, +using JavaScript. + +{{index "getContext method", [canvas, context]}} + +You create a ((context)) with the `getContext` method on the +`` DOM element. + +```{lang: "text/html"} +

Before canvas.

+ +

After canvas.

+ +``` + +After creating the context object, the example draws a red +((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left +corner at coordinates (10,10). + +{{if book + +{{figure {url: "img/canvas_fill.png", alt: "A canvas with a rectangle",width: "2.5cm"}}} + +if}} + +{{index SVG, coordinates}} + +Just like in HTML (and SVG), the coordinate system that the canvas +uses puts (0,0) at the top-left corner, and the positive y-((axis)) +goes down from there. So (10,10) is 10 pixels below and to the right +of the top-left corner. + +{{id fill_stroke}} + +## Lines and surfaces + +{{index filling, stroking, drawing, SVG}} + +In the ((canvas)) interface, a shape can be _filled_, meaning its area +is given a certain color or pattern, or it can be _stroked_, which +means a ((line)) is drawn along its edge. The same terminology is used +by SVG. + +{{index "fillRect method", "strokeRect method"}} + +The `fillRect` method fills a ((rectangle)). It takes first the x- and +y-((coordinates)) of the rectangle's top-left corner, then its width, +and then its height. A similar method, `strokeRect`, draws the +((outline)) of a rectangle. + +{{index [state, "of canvas"]}} + +Neither method takes any further parameters. The color of the fill, +thickness of the stroke, and so on, are not determined by an argument +to the method (as you might reasonably expect) but rather by +properties of the context object. + +{{index filling, "fillStyle property"}} + +The `fillStyle` property controls the way shapes are filled. It can be +set to a string that specifies a ((color)), using the color notation +used by ((CSS)). + +{{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} + +The `strokeStyle` property works similarly but determines the color +used for a stroked line. The width of that line is determined by the +`lineWidth` property, which may contain any positive number. + +```{lang: "text/html"} + + +``` + +{{if book + +This code draws two blue squares, using a thicker line for the second +one. + +{{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} + +if}} + +{{index "default value", [canvas, size]}} + +When no `width` or `height` attribute is specified, as in the example, +a canvas element gets a default width of 300 pixels and height of 150 +pixels. + +## Paths + +{{index [path, canvas], [interface, design], [canvas, path]}} + +A path is a sequence of ((line))s. The 2D canvas interface takes a +peculiar approach to describing such a path. It is done entirely +through ((side effect))s. Paths are not values that can be stored and +passed around. Instead, if you want to do something with a path, you +make a sequence of method calls to describe its shape. + +```{lang: "text/html"} + + +``` + +{{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} + +This example creates a path with a number of horizontal ((line)) +segments and then strokes it using the `stroke` method. Each segment +created with `lineTo` starts at the path's _current_ position. That +position is usually the end of the last segment, unless `moveTo` was +called. In that case, the next segment would start at the position +passed to `moveTo`. + +{{if book + +The path described by the previous program looks like this: + +{{figure {url: "img/canvas_path.png", alt: "Stroking a number of lines",width: "2.1cm"}}} + +if}} + +{{index [path, canvas], filling, [path, closing], "fill method"}} + +When filling a path (using the `fill` method), each ((shape)) is +filled separately. A path can contain multiple shapes—each `moveTo` +motion starts a new one. But the path needs to be _closed_ (meaning +its start and end are in the same position) before it can be filled. +If the path is not already closed, a line is added from its end to its +start, and the shape enclosed by the completed path is filled. + +```{lang: "text/html"} + + +``` + +This example draws a filled triangle. Note that only two of the +triangle's sides are explicitly drawn. The third, from the +bottom-right corner back to the top, is implied and wouldn't be there +when you stroke the path. + +{{if book + +{{figure {url: "img/canvas_triangle.png", alt: "Filling a path",width: "2.2cm"}}} + +if}} + +{{index "stroke method", "closePath method", [path, closing], canvas}} + +You could also use the `closePath` method to explicitly close a path +by adding an actual ((line)) segment back to the path's start. This +segment _is_ drawn when stroking the path. + +## Curves + +{{index [path, canvas], canvas, drawing}} + +A path may also contain ((curve))d ((line))s. These are unfortunately +a bit more involved to draw. + +{{index "quadraticCurveTo method"}} + +The `quadraticCurveTo` method draws a curve to a given point. To +determine the curvature of the line, the method is given a ((control +point)) as well as a destination point. Imagine this control point as +_attracting_ the line, giving it its curve. The line won't go through +the control point, but its direction at the start and end points will +be such that a straight line in that direction would point toward the +control point. The following example illustrates this: + +```{lang: "text/html"} + + +``` + +{{if book + +It produces a path that looks like this: + +{{figure {url: "img/canvas_quadraticcurve.png", alt: "A quadratic curve",width: "2.3cm"}}} + +if}} + +{{index "stroke method"}} + +We draw a ((quadratic curve)) from the left to the right, with (60,10) +as control point, and then draw two ((line)) segments going through +that control point and back to the start of the line. The result +somewhat resembles a _((Star Trek))_ insignia. You can see the effect +of the control point: the lines leaving the lower corners start off in +the direction of the control point and then ((curve)) toward their +target. + +{{index canvas, "bezierCurveTo method"}} + +The `bezierCurveTo` method draws a similar kind of curve. Instead of a +single ((control point)), this one has two—one for each of the +((line))'s endpoints. Here is a similar sketch to illustrate the +behavior of such a curve: + +```{lang: "text/html"} + + +``` + +The two control points specify the direction at both ends of the +curve. The farther they are away from their corresponding point, the +more the curve will "bulge" in that direction. + +{{if book + +{{figure {url: "img/canvas_beziercurve.png", alt: "A bezier curve",width: "2.2cm"}}} + +if}} + +{{index "trial and error"}} + +Such ((curve))s can be hard to work with—it's not always clear how to +find the ((control point))s that provide the ((shape)) you are looking +for. Sometimes you can compute them, and sometimes you'll just have to +find a suitable value by trial and error. + +{{index "arc method", arc}} + +The `arc` method is a way to draw a line that curves along the edge of +a circle. It takes a pair of ((coordinates)) for the arc's center, a +radius, and then a start angle and end angle. + +{{index pi, "Math.PI constant"}} + +Those last two parameters make it possible to draw only part of the +circle. The ((angle))s are measured in ((radian))s, not ((degree))s. +This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, +which is about 6.28. The angle starts counting at the point to the +right of the circle's center and goes clockwise from there. You can +use a start of 0 and an end bigger than 2π (say, 7) to draw a full +circle. + +```{lang: "text/html"} + + +``` + +{{index "moveTo method", "arc method", [path, " canvas"]}} + +The resulting picture contains a ((line)) from the right of the full +circle (first call to `arc`) to the right of the quarter-((circle)) +(second call). Like other path-drawing methods, a line drawn with +`arc` is connected to the previous path segment. You can call `moveTo` +or start a new path to avoid this. + +{{if book + +{{figure {url: "img/canvas_circle.png", alt: "Drawing a circle",width: "4.9cm"}}} + +if}} + +{{id pie_chart}} + +## Drawing a pie chart + +{{index "pie chart example"}} + +Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your +first assignment is to draw a pie chart of its customer satisfaction +((survey)) results. + +The `results` binding contains an array of objects that represent the +survey responses. + +```{sandbox: "pie", includeCode: true} +const results = [ + {name: "Satisfied", count: 1043, color: "lightblue"}, + {name: "Neutral", count: 563, color: "lightgreen"}, + {name: "Unsatisfied", count: 510, color: "pink"}, + {name: "No comment", count: 175, color: "silver"} +]; +``` + +{{index "pie chart example"}} + +To draw a pie chart, we draw a number of pie slices, each made up of +an ((arc)) and a pair of ((line))s to the center of that arc. We can +compute the ((angle)) taken up by each arc by dividing a full circle +(2π) by the total number of responses and then multiplying that number +(the angle per response) by the number of people who picked a given +choice. + +```{lang: "text/html", sandbox: "pie"} + + +``` + +{{if book + +This draws the following chart: + +{{figure {url: "img/canvas_pie_chart.png", alt: "A pie chart",width: "5cm"}}} + +if}} + +But a chart that doesn't tell us what the slices mean isn't very +helpful. We need a way to draw text to the ((canvas)). + +## Text + +{{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} + +A 2D canvas drawing context provides the methods `fillText` and +`strokeText`. The latter can be useful for outlining letters, but +usually `fillText` is what you need. It will fill the outline of the +given ((text)) with the current `fillStyle`. + +```{lang: "text/html"} + + +``` + +You can specify the size, style, and ((font)) of the text with the +`font` property. This example just gives a font size and family name. +It is also possible to add `italic` or `bold` to the start of the +string to select a style. + +{{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} + +The last two arguments to `fillText` and `strokeText` provide the +position at which the font is drawn. By default, they indicate the +position of the start of the text's alphabetic baseline, which is the +line that letters "stand" on, not counting hanging parts in letters +such as _j_ or _p_. You can change the horizontal position by setting the +`textAlign` property to `"end"` or `"center"` and the vertical +position by setting `textBaseline` to `"top"`, `"middle"`, or +`"bottom"`. + +{{index "pie chart example"}} + +We'll come back to our pie chart, and the problem of ((label))ing the +slices, in the [exercises](canvas#exercise_pie_chart) at the end of +the chapter. + +## Images + +{{index "vector graphics", "bitmap graphics"}} + +In computer ((graphics)), a distinction is often made between _vector_ +graphics and _bitmap_ graphics. The first is what we have been doing +so far in this chapter—specifying a picture by giving a logical +description of ((shape))s. Bitmap graphics, on the other hand, don't +specify actual shapes but rather work with ((pixel)) data (rasters of +colored dots). + +{{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} + +The `drawImage` method allows us to draw ((pixel)) data onto a +((canvas)). This pixel data can originate from an `` element or +from another canvas. The following example creates a detached `` +element and loads an image file into it. But it cannot immediately +start drawing from this picture because the browser may not have +loaded it yet. To deal with this, we register a `"load"` event handler +and do the drawing after the image has loaded. + +```{lang: "text/html"} + + +``` + +{{index "drawImage method", scaling}} + +By default, `drawImage` will draw the image at its original size. You +can also give it two additional arguments to set a different width +and height. + +When `drawImage` is given _nine_ arguments, it can be used to draw +only a fragment of an image. The second through fifth arguments +indicate the rectangle (x, y, width, and height) in the source image +that should be copied, and the sixth to ninth arguments give the +rectangle (on the canvas) into which it should be copied. + +{{index "player", "pixel art"}} + +This can be used to pack multiple _((sprite))s_ (image elements) into +a single image file and then draw only the part you need. For example, +we have this picture containing a game character in multiple +((pose))s: + +{{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} + +{{index [animation, "platform game"]}} + +By alternating which pose we draw, we can show an animation that +looks like a walking character. + +{{index "fillRect method", "clearRect method", clearing}} + +To animate a ((picture)) on a ((canvas)), the `clearRect` method is +useful. It resembles `fillRect`, but instead of coloring the +rectangle, it makes it ((transparent)), removing the previously drawn +pixels. + +{{index "setInterval function", "img (HTML tag)"}} + +We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide +and 30 pixels high. The following code loads the image and then sets +up an interval (repeated timer) to draw the next ((frame)): + +```{lang: "text/html"} + + +``` + +{{index "remainder operator", "% operator", [animation, "platform game"]}} + +The `cycle` binding tracks our position in the animation. For each +((frame)), it is incremented and then clipped back to the 0 to 7 range +by using the remainder operator. This binding is then used to compute +the x-coordinate that the sprite for the current pose has in the +picture. + +## Transformation + +{{index transformation, mirroring}} + +{{indexsee flipping, mirroring}} + +But what if we want our character to walk to the left instead of to +the right? We could draw another set of sprites, of course. But we can +also instruct the ((canvas)) to draw the picture the other way round. + +{{index "scale method", scaling}} + +Calling the `scale` method will cause anything drawn after it to be +scaled. This method takes two parameters, one to set a horizontal +scale and one to set a vertical scale. + +```{lang: "text/html"} + + +``` + +{{if book + +Because of the call to `scale`, the circle is drawn three times as wide +and half as high. + +{{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} + +if}} + +{{index mirroring}} + +Scaling will cause everything about the drawn image, including the +((line width)), to be stretched out or squeezed together as specified. +Scaling by a negative amount will flip the picture around. The +flipping happens around point (0,0), which means it will also flip the +direction of the coordinate system. When a horizontal scaling of -1 is +applied, a shape drawn at x position 100 will end up at what used to +be position -100. + +{{index "drawImage method"}} + +So to turn a picture around, we can't simply add `cx.scale(-1, 1)` +before the call to `drawImage` because that would move our picture +outside of the ((canvas)), where it won't be visible. You could adjust +the ((coordinates)) given to `drawImage` to compensate for this by +drawing the image at x position -50 instead of 0. Another solution, +which doesn't require the code that does the drawing to know about the +scale change, is to adjust the ((axis)) around which the scaling +happens. + +{{index "rotate method", "translate method", transformation}} + +There are several other methods besides `scale` that influence the +coordinate system for a ((canvas)). You can rotate subsequently drawn +shapes with the `rotate` method and move them with the `translate` +method. The interesting—and confusing—thing is that these +transformations _stack_, meaning that each one happens relative to the +previous transformations. + +{{index "rotate method", "translate method"}} + +So if we translate by 10 horizontal pixels twice, everything will be +drawn 20 pixels to the right. If we first move the center of the +coordinate system to (50,50) and then rotate by 20 ((degree))s (about +0.1π ((radian))s), that rotation will happen _around_ point (50,50). + +{{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} + +{{index coordinates}} + +But if we _first_ rotate by 20 degrees and _then_ translate by +(50,50), the translation will happen in the rotated coordinate system +and thus produce a different orientation. The order in which +transformations are applied matters. + +{{index axis, mirroring}} + +To flip a picture around the vertical line at a given x position, we +can do the following: + +```{includeCode: true} +function flipHorizontally(context, around) { + context.translate(around, 0); + context.scale(-1, 1); + context.translate(-around, 0); +} +``` + +{{index "flipHorizontally method"}} + +We move the y-((axis)) to where we want our ((mirror)) to be, apply +the mirroring, and finally move the y-axis back to its proper place in +the mirrored universe. The following picture explains why this works: + +{{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} + +{{index "translate method", "scale method", transformation, canvas}} + +This shows the coordinate systems before and after mirroring across +the central line. The triangles are numbered to illustrate each step. +If we draw a triangle at a positive x position, it would, by default, +be in the place where triangle 1 is. A call to `flipHorizontally` +first does a translation to the right, which gets us to triangle 2. It +then scales, flipping the triangle over to position 3. This is not +where it should be, if it were mirrored in the given line. The second +`translate` call fixes this—it "cancels" the initial translation and +makes triangle 4 appear exactly where it should. + +We can now draw a mirrored character at position (100,0) by flipping +the world around the character's vertical center. + +```{lang: "text/html"} + + +``` + +## Storing and clearing transformations + +{{index "side effect", canvas, transformation}} + +Transformations stick around. Everything else we draw after +((drawing)) that mirrored character would also be mirrored. That might +be inconvenient. + +It is possible to save the current transformation, do some drawing and +transforming, and then restore the old transformation. This is usually +the proper thing to do for a function that needs to temporarily +transform the coordinate system. First, we save whatever +transformation the code that called the function was using. Then the +function does its thing, adding more transformations on top of the +current transformation. Finally, we revert to the +transformation we started with. + +{{index "save method", "restore method", [state, "of canvas"]}} + +The `save` and `restore` methods on the 2D ((canvas)) context do this +((transformation)) management. They conceptually keep a stack of +transformation states. When you call `save`, the current state is +pushed onto the stack, and when you call `restore`, the state on top +of the stack is taken off and used as the context's current +transformation. You can also call `resetTransform` to fully reset the +transformation. + +{{index "branching recursion", "fractal example", recursion}} + +The `branch` function in the following example illustrates what you +can do with a function that changes the transformation and then calls +a function (in this case itself), which continues drawing with +the given transformation. + +This function draws a treelike shape by drawing a line, moving the +center of the coordinate system to the end of the line, and calling +itself twice—first rotated to the left and then rotated to the right. +Every call reduces the length of the branch drawn, and the recursion +stops when the length drops below 8. + +```{lang: "text/html"} + + +``` + +{{if book + +The result is a simple fractal. + +{{figure {url: "img/canvas_tree.png", alt: "A recursive picture",width: "5cm"}}} + +if}} + +{{index "save method", "restore method", canvas, "rotate method"}} + +If the calls to `save` and `restore` were not there, the second +recursive call to `branch` would end up with the position and rotation +created by the first call. It wouldn't be connected to the current +branch but rather to the innermost, rightmost branch drawn by the +first call. The resulting shape might also be interesting, but it is +definitely not a tree. + +{{id canvasdisplay}} + +## Back to the game + +{{index "drawImage method"}} + +We now know enough about ((canvas)) drawing to start working on a +((canvas))-based ((display)) system for the ((game)) from the +[previous chapter](game). The new display will no longer be showing +just colored boxes. Instead, we'll use `drawImage` to draw pictures +that represent the game's elements. + +{{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} + +We define another display object type called `CanvasDisplay`, +supporting the same interface as `DOMDisplay` from [Chapter +?](game#domdisplay), namely, the methods `syncState` and `clear`. + +{{index [state, "in objects"]}} + +This object keeps a little more information than `DOMDisplay`. Rather +than using the scroll position of its DOM element, it tracks its own +((viewport)), which tells us what part of the level we are currently +looking at. Finally, it keeps a `flipPlayer` property so that even +when the player is standing still, it keeps facing the direction it +last moved in. + +```{sandbox: "game", includeCode: true} +class CanvasDisplay { + constructor(parent, level) { + this.canvas = document.createElement("canvas"); + this.canvas.width = Math.min(600, level.width * scale); + this.canvas.height = Math.min(450, level.height * scale); + parent.appendChild(this.canvas); + this.cx = this.canvas.getContext("2d"); + + this.flipPlayer = false; + + this.viewport = { + left: 0, + top: 0, + width: this.canvas.width / scale, + height: this.canvas.height / scale + }; + } + + clear() { + this.canvas.remove(); + } +} +``` + +The `syncState` method first computes a new viewport and then draws +the game scene at the appropriate position. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.syncState = function(state) { + this.updateViewport(state); + this.clearDisplay(state.status); + this.drawBackground(state.level); + this.drawActors(state.actors); +}; +``` + +{{index scrolling, clearing}} + +Contrary to `DOMDisplay`, this display style _does_ have to redraw the +background on every update. Because shapes on a canvas are just +((pixel))s, after we draw them there is no good way to move them (or +remove them). The only way to update the canvas display is to clear it +and redraw the scene. We may also have scrolled, which requires the +background to be in a different position. + +{{index "CanvasDisplay class"}} + +The `updateViewport` method is similar to `DOMDisplay`'s +`scrollPlayerIntoView` method. It checks whether the player is too +close to the edge of the screen and moves the ((viewport)) when this +is the case. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.updateViewport = function(state) { + let view = this.viewport, margin = view.width / 3; + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)); + + if (center.x < view.left + margin) { + view.left = Math.max(center.x - margin, 0); + } else if (center.x > view.left + view.width - margin) { + view.left = Math.min(center.x + margin - view.width, + state.level.width - view.width); + } + if (center.y < view.top + margin) { + view.top = Math.max(center.y - margin, 0); + } else if (center.y > view.top + view.height - margin) { + view.top = Math.min(center.y + margin - view.height, + state.level.height - view.height); + } +}; +``` + +{{index boundary, "Math.max function", "Math.min function", clipping}} + +The calls to `Math.max` and `Math.min` ensure that the viewport does +not end up showing space outside of the level. `Math.max(x, 0)` makes +sure the resulting number is not less than zero. `Math.min` +similarly guarantees that a value stays below a given bound. + +When ((clearing)) the display, we'll use a slightly different +((color)) depending on whether the game is won (brighter) or lost +(darker). + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.clearDisplay = function(status) { + if (status == "won") { + this.cx.fillStyle = "rgb(68, 191, 255)"; + } else if (status == "lost") { + this.cx.fillStyle = "rgb(44, 136, 214)"; + } else { + this.cx.fillStyle = "rgb(52, 166, 251)"; + } + this.cx.fillRect(0, 0, + this.canvas.width, this.canvas.height); +}; +``` + +{{index "Math.floor function", "Math.ceil function", rounding}} + +To draw the background, we run through the tiles that are visible in +the current viewport, using the same trick used in the `touches` +method from the [previous chapter](game#touches). + +```{sandbox: "game", includeCode: true} +let otherSprites = document.createElement("img"); +otherSprites.src = "img/sprites.png"; + +CanvasDisplay.prototype.drawBackground = function(level) { + let {left, top, width, height} = this.viewport; + let xStart = Math.floor(left); + let xEnd = Math.ceil(left + width); + let yStart = Math.floor(top); + let yEnd = Math.ceil(top + height); + + for (let y = yStart; y < yEnd; y++) { + for (let x = xStart; x < xEnd; x++) { + let tile = level.rows[y][x]; + if (tile == "empty") continue; + let screenX = (x - left) * scale; + let screenY = (y - top) * scale; + let tileX = tile == "lava" ? scale : 0; + this.cx.drawImage(otherSprites, + tileX, 0, scale, scale, + screenX, screenY, scale, scale); + } + } +}; +``` + +{{index "drawImage method", sprite, tile}} + +Tiles that are not empty are drawn with `drawImage`. The +`otherSprites` image contains the pictures used for elements other +than the player. It contains, from left to right, the wall tile, the +lava tile, and the sprite for a coin. + +{{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} + +{{index scaling}} + +Background tiles are 20 by 20 pixels since we will use the same scale +that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 +(the value of the `scale` binding), and the offset for walls is 0. + +{{index drawing, "load event", "drawImage method"}} + +We don't bother waiting for the sprite image to load. Calling +`drawImage` with an image that hasn't been loaded yet will simply do +nothing. Thus, we might fail to draw the game properly for the first +few ((frame))s, while the image is still loading, but that is not a +serious problem. Since we keep updating the screen, the correct scene +will appear as soon as the loading finishes. + +{{index "player", [animation, "platform game"], drawing}} + +The ((walking)) character shown earlier will be used to represent the +player. The code that draws it needs to pick the right ((sprite)) and +direction based on the player's current motion. The first eight +sprites contain a walking animation. When the player is moving along a +floor, we cycle through them based on the current time. We want to +switch frames every 60 milliseconds, so the ((time)) is divided by 60 +first. When the player is standing still, we draw the ninth sprite. +During jumps, which are recognized by the fact that the vertical speed +is not zero, we use the tenth, rightmost sprite. + +{{index "flipHorizontally function", "CanvasDisplay class"}} + +Because the ((sprite))s are slightly wider than the player object—24 +instead of 16 pixels to allow some space for feet and arms—the method +has to adjust the x-coordinate and width by a given amount +(`playerXOverlap`). + +```{sandbox: "game", includeCode: true} +let playerSprites = document.createElement("img"); +playerSprites.src = "img/player.png"; +const playerXOverlap = 4; + +CanvasDisplay.prototype.drawPlayer = function(player, x, y, + width, height){ + width += playerXOverlap * 2; + x -= playerXOverlap; + if (player.speed.x != 0) { + this.flipPlayer = player.speed.x < 0; + } + + let tile = 8; + if (player.speed.y != 0) { + tile = 9; + } else if (player.speed.x != 0) { + tile = Math.floor(Date.now() / 60) % 8; + } + + this.cx.save(); + if (this.flipPlayer) { + flipHorizontally(this.cx, x + width / 2); + } + let tileX = tile * width; + this.cx.drawImage(playerSprites, tileX, 0, width, height, + x, y, width, height); + this.cx.restore(); +}; +``` + +The `drawPlayer` method is called by `drawActors`, which is +responsible for drawing all the actors in the game. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.drawActors = function(actors) { + for (let actor of actors) { + let width = actor.size.x * scale; + let height = actor.size.y * scale; + let x = (actor.pos.x - this.viewport.left) * scale; + let y = (actor.pos.y - this.viewport.top) * scale; + if (actor.type == "player") { + this.drawPlayer(actor, x, y, width, height); + } else { + let tileX = (actor.type == "coin" ? 2 : 1) * scale; + this.cx.drawImage(otherSprites, + tileX, 0, width, height, + x, y, width, height); + } + } +}; +``` + +When ((drawing)) something that is not the ((player)), we look at its +type to find the offset of the correct sprite. The ((lava)) tile is +found at offset 20, and the ((coin)) sprite is found at 40 (two times +`scale`). + +{{index viewport}} + +We have to subtract the viewport's position when computing the actor's +position since (0,0) on our ((canvas)) corresponds to the top left of +the viewport, not the top left of the level. We could also have used +`translate` for this. Either way works. + +{{if interactive + +This document plugs the new display into `runGame`: + +```{lang: "text/html", sandbox: game, focus: yes, startCode: true} + + + +``` + +if}} + +{{if book + +{{index [game, screenshot], [game, "with canvas"]}} + +That concludes the new ((display)) system. The resulting game looks +something like this: + +{{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} + +if}} + +{{id graphics_tradeoffs}} + +## Choosing a graphics interface + +So when you need to generate graphics in the browser, you can choose +between plain HTML, ((SVG)), and ((canvas)). There is no single +_best_ approach that works in all situations. Each option has +strengths and weaknesses. + +{{index "text wrapping"}} + +Plain HTML has the advantage of being simple. It also integrates well +with ((text)). Both SVG and canvas allow you to draw text, but they +won't help you position that text or wrap it when it takes up more +than one line. In an HTML-based picture, it is much easier to include +blocks of text. + +{{index zooming, SVG}} + +SVG can be used to produce ((crisp)) ((graphics)) that look good at +any zoom level. Unlike HTML, it is designed for drawing +and is thus more suitable for that purpose. + +{{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} + +Both SVG and HTML build up a data structure (the DOM) that +represents your picture. This makes it possible to modify elements +after they are drawn. If you need to repeatedly change a small part of +a big ((picture)) in response to what the user is doing or as part of +an ((animation)), doing it in a canvas can be needlessly expensive. +The DOM also allows us to register mouse event handlers on every +element in the picture (even on shapes drawn with SVG). You can't do +that with canvas. + +{{index performance, optimization}} + +But ((canvas))'s ((pixel))-oriented approach can be an advantage when +drawing a huge number of tiny elements. The fact that it does not +build up a data structure but only repeatedly draws onto the same +pixel surface gives canvas a lower cost per shape. + +{{index "ray tracer"}} + +There are also effects, such as rendering a scene one pixel at a time +(for example, using a ray tracer) or postprocessing an image with +JavaScript (blurring or distorting it), that can be realistically +handled only by a ((pixel))-based approach. + +In some cases, you may want to combine several of these techniques. +For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but +show ((text))ual information by positioning an HTML element on top +of the picture. + +{{index display}} + +For nondemanding applications, it really doesn't matter much which +interface you choose. The display we built for our game in this +chapter could have been implemented using any of these three +((graphics)) technologies since it does not need to draw text, handle +mouse interaction, or work with an extraordinarily large number of +elements. + +## Summary + +In this chapter we discussed techniques for drawing graphics in the +browser, focusing on the `` element. + +A canvas node represents an area in a document that our program may +draw on. This drawing is done through a drawing context object, +created with the `getContext` method. + +The 2D drawing interface allows us to fill and stroke various shapes. +The context's `fillStyle` property determines how shapes are filled. +The `strokeStyle` and `lineWidth` properties control the way lines are +drawn. + +Rectangles and pieces of text can be drawn with a single method call. +The `fillRect` and `strokeRect` methods draw rectangles, and the +`fillText` and `strokeText` methods draw text. To create custom +shapes, we must first build up a path. + +{{index stroking, filling}} + +Calling `beginPath` starts a new path. A number of other methods add +lines and curves to the current path. For example, `lineTo` can add a +straight line. When a path is finished, it can be filled with the +`fill` method or stroked with the `stroke` method. + +Moving pixels from an image or another canvas onto our canvas is done +with the `drawImage` method. By default, this method draws the whole +source image, but by giving it more parameters, you can copy a +specific area of the image. We used this for our game by copying +individual poses of the game character out of an image that contained +many such poses. + +Transformations allow you to draw a shape in multiple orientations. A +2D drawing context has a current transformation that can be changed +with the `translate`, `scale`, and `rotate` methods. These will affect +all subsequent drawing operations. A transformation state can be saved +with the `save` method and restored with the `restore` method. + +When showing an animation on a canvas, the `clearRect` method can be +used to clear part of the canvas before redrawing it. + +## Exercises + +### Shapes + +{{index "shapes (exercise)"}} + +Write a program that draws the following ((shape))s on a ((canvas)): + +{{index rotation}} + +1. A ((trapezoid)) (a ((rectangle)) that is wider on one side) + +2. A red ((diamond)) (a rectangle rotated 45 degrees or ¼π radians) + +3. A zigzagging ((line)) + +4. A ((spiral)) made up of 100 straight line segments + +5. A yellow ((star)) + +{{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} + +When drawing the last two, you may want to refer to the explanation of +`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes +how to get coordinates on a circle using these functions. + +{{index readability, "hard-coding"}} + +I recommend creating a function for each shape. Pass the position, and +optionally other properties such as the size or the number of points, +as parameters. The alternative, which is to hard-code numbers all over +your code, tends to make the code needlessly hard to read and modify. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index [path, canvas], "shapes (exercise)"}} + +The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable +center coordinates and add each of the four corners around the center. + +{{index "flipHorizontally function", rotation}} + +The ((diamond)) (2) can be drawn the straightforward way, with a path, +or the interesting way, with a `rotate` ((transformation)). To use +rotation, you will have to apply a trick similar to what we did in the +`flipHorizontally` function. Because you want to rotate around the +center of your rectangle and not around the point (0,0), you must +first `translate` to there, then rotate, and then translate back. + +Make sure you reset the transformation after drawing any shape that +creates one. + +{{index "remainder operator", "% operator"}} + +For the ((zigzag)) (3) it becomes impractical to write a new call to +`lineTo` for each line segment. Instead, you should use a ((loop)). +You can have each iteration draw either two ((line)) segments (right +and then left again) or one, in which case you must use the evenness +(`% 2`) of the loop index to determine whether to go left or right. + +You'll also need a loop for the ((spiral)) (4). If you draw a series +of points, with each point moving further along a circle around the +spiral's center, you get a circle. If, during the loop, you vary the +radius of the circle on which you are putting the current point and go +around more than once, the result is a spiral. + +{{index "quadraticCurveTo method"}} + +The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. +You could also draw one with straight lines. Divide a circle into +eight pieces for a star with eight points, or however many pieces you +want. Draw lines between these points, making them curve toward the +center of the star. With `quadraticCurveTo`, you can use the center as +the control point. + +hint}} + +{{id exercise_pie_chart}} + +### The pie chart + +{{index label, text, "pie chart example"}} + +[Earlier](canvas#pie_chart) in the chapter, we saw an example program +that drew a pie chart. Modify this program so that the name of each +category is shown next to the slice that represents it. Try to find a +pleasing-looking way to automatically position this text that would +work for other data sets as well. You may assume that categories are +big enough to leave ample room for their labels. + +You might need `Math.sin` and `Math.cos` again, which are described in +[Chapter ?](dom#sin_cos). + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} + +You will need to call `fillText` and set the context's `textAlign` and +`textBaseline` properties in such a way that the text ends up where +you want it. + +A sensible way to position the labels would be to put the text on the +line going from the center of the pie through the middle of the slice. +You don't want to put the text directly against the side of the pie +but rather move the text out to the side of the pie by a given number +of pixels. + +The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The +following code finds a position on this line 120 pixels from the +center: + +```{test: no} +let middleAngle = currentAngle + 0.5 * sliceAngle; +let textX = Math.cos(middleAngle) * 120 + centerX; +let textY = Math.sin(middleAngle) * 120 + centerY; +``` + +For `textBaseline`, the value `"middle"` is probably appropriate when +using this approach. What to use for `textAlign` depends on which side +of the circle we are on. On the left, it should be `"right"`, and on +the right, it should be `"left"`, so that the text is positioned away +from the pie. + +{{index "Math.cos function"}} + +If you are not sure how to find out which side of the circle a given +angle is on, look to the explanation of `Math.cos` in [Chapter +?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it +corresponds to, which in turn tells us exactly which side of the +circle we are on. + +hint}} + +### A bouncing ball + +{{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} + +Use the `requestAnimationFrame` technique that we saw in [Chapter +?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a +((box)) with a bouncing ((ball)) in it. The ball moves at a constant +((speed)) and bounces off the box's sides when it hits them. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "strokeRect method", animation, "arc method"}} + +A ((box)) is easy to draw with `strokeRect`. Define a binding that +holds its size or define two bindings if your box's width and height +differ. To create a round ((ball)), start a path and call `arc(x, y, +radius, 0, 7)`, which creates an arc going from zero to more than a +whole circle. Then fill the path. + +{{index "collision detection", "Vec class"}} + +To model the ball's position and ((speed)), you can use the `Vec` +class from [Chapter ?](game#vector)[ (which is available on this +page)]{if interactive}. Give it a starting speed, preferably one that +is not purely vertical or horizontal, and for every ((frame)) multiply +that speed by the amount of time that elapsed. When the ball gets +too close to a vertical wall, invert the x component in its speed. +Likewise, invert the y component when it hits a horizontal wall. + +{{index "clearRect method", clearing}} + +After finding the ball's new position and speed, use `clearRect` to +delete the scene and redraw it using the new position. + +hint}} + +### Precomputed mirroring + +{{index optimization, "bitmap graphics", mirror}} + +One unfortunate thing about ((transformation))s is that they slow down +the drawing of bitmaps. The position and size of each ((pixel)) has to be +transformed, and though it is possible that ((browser))s will get +cleverer about transformation in the ((future)), they currently cause a +measurable increase in the time it takes to draw a bitmap. + +In a game like ours, where we are drawing only a single transformed +sprite, this is a nonissue. But imagine that we need to draw hundreds +of characters or thousands of rotating particles from an explosion. + +Think of a way to allow us to draw an inverted character without +loading additional image files and without having to make transformed +`drawImage` calls every frame. + +{{hint + +{{index mirror, scaling, "drawImage method"}} + +The key to the solution is the fact that we can use a ((canvas)) +element as a source image when using `drawImage`. It is possible to +create an extra `` element, without adding it to the document, +and draw our inverted sprites to it, once. When drawing an actual +frame, we just copy the already inverted sprites to the main canvas. + +{{index "load event"}} + +Some care would be required because images do not load instantly. We +do the inverted drawing only once, and if we do it before the image +loads, it won't draw anything. A `"load"` handler on the image can be +used to draw the inverted images to the extra canvas. This canvas can +be used as a drawing source immediately (it'll simply be blank until +we draw the character onto it). + +hint}} + From fe385b2f89658deb34df4cdddd6b7365150fa202 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:36:02 -0500 Subject: [PATCH 02/22] =?UTF-8?q?Segunda=20secci=C3=B3n:=20SVG=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 024ef9ba5..4e17c6524 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -50,16 +50,15 @@ con la figura en una nueva posición. ## SVG -This book will not go into ((SVG)) in detail, but I will briefly -explain how it works. At the [end of the -chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs -that you must consider when deciding which ((drawing)) mechanism is -appropriate for a given application. +Este libro no hablará de ((SVG)) a detalle, pero explicaré +de forma breve como funciona. Al final de [final del capítulo](canvas#graphics_tradeoffs), regresaré a estos temas +que debes considerar cuando debas decidir cuál mecanismo de ((dibujo)) sea +apropiado dada una aplicación. -This is an HTML document with a simple SVG ((picture)) in it: +Este es un documento HTML con una ((imagen)) SVG simple: ```{lang: "text/html", sandbox: "svg"} -

Normal HTML here.

+

HTML normal aquí.

` -and `` tags, which do not exist in HTML, do have a meaning in -SVG—they draw shapes using the style and position specified by their -attributes. +El atributo `xmlns` cambia un elemento (y sus hijos) a un +_XML namespace_ diferente. Este _namespace_, identificado por una ((URL)), +especifica el dialecto que estamos usando. las etiquetas +`` y ``, —que no existen en HTML, pero tienen un significado en +SVG— dibujan formas usando el estilo y posición especificados por sus +atributos. {{if book -The document is displayed like this: +El documento muestra algo así: -{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} +{{figure {url: "img/svg-demo.png", alt: "Una imagen SVG embebida",width: "4.5cm"}}} if}} {{index [DOM, graphics]}} -These tags create DOM elements, just like HTML tags, that -scripts can interact with. For example, this changes the `` -element to be ((color))ed cyan instead: +Estas etiquetas crean elementos en el DOM, como etiquetas de HTML, con las +que los scripts pueden interactuar. Por ejemplo, el siguiente código cambia el elemento `` +para que sea ((color))eado de cyan: ```{sandbox: "svg"} -let circle = document.querySelector("circle"); -circle.setAttribute("fill", "cyan"); +let circulo = document.querySelector("circle"); +circulo.setAttribute("fill", "cyan"); ``` -## The canvas element +## El elemento canvas {{index [canvas, size], "canvas (HTML tag)"}} From e75d613f180795435a3a2db928c1128e9a707c5e Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:12:23 -0500 Subject: [PATCH 03/22] =?UTF-8?q?Primera=20secci=C3=B3n=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 1520 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1520 insertions(+) create mode 100644 17_canvas.md diff --git a/17_canvas.md b/17_canvas.md new file mode 100644 index 000000000..024ef9ba5 --- /dev/null +++ b/17_canvas.md @@ -0,0 +1,1520 @@ +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} + +# Dibujando en Canvas + +{{quote {author: "M.C. Escher", title: "citado por Bruno Ernst en The Magic Mirror of M.C. Escher", chapter: true} + +El dibujo es decepción. + +quote}} + +{{index "Escher, M.C."}} + +{{figure {url: "img/chapter_picture_17.jpg", alt: "Imagen de un brazo robótico dibujando en un papel", chapter: "framed"}}} + +{{index CSS, "transform (CSS)", [DOM, graphics]}} + +Los navegadores nos proporcionan varias formas de mostrar((gráficos)). La más simple +es usar estilos para posiciones y colores de elementos regulares del DOM. +Esto puede ser tardado, como vimos en el [previous +chapter](juego) pasado. Agregando ((imagenes))s de fondo transparentes + a los nodos, podemos hacer que se vean exactamente de la form que +queremos. incluso es posible rotar o distorsionar nodos con el estilo `transform`. + +Pero estaríamos usando el DOM para algo para lo que +no fue diseñado. Algunas tareas, como dibujar una ((línea)) entre +puntos arbitrarios, son extremadamente incómodas de hacer con elementos HTML +regulares. + +{{index SVG, "img (HTML tag)"}} + +Tenemos dos alternativas. La primera es basada en el DOM, pero utiliza +_Scalable Vector Graphics_ (SVG), más que HTML. Piensa en SVG como un +dialecto de un ((documento)) de marcado que se centra en ((figura))s más que en +texto. Puedes embeber un documento SVG directamente en un archivo HTML o +puedes incluirlo con una etiqueta ``. + +{{index clearing, [DOM graphics], [interface, canvas]}} + +La segunda alternativa es llamada _((canvas))_. Un canvas es un +elemento del DOM que encapsula una ((imagen)). Proporciona una +intefaz de programción para dibujar ((forma))s en el espacio +del nodo. La principal diferencia entre un canvas y una imagen +SVG es que en SVG la descripción original de las figuras es +preservada de manera que puedan ser movidas o reescaladas en cualquier momento. Un canvas, +por otro lado, convierte las figuras a ((pixele))s (puntos de color en +una rejilla) tan pronto son dibujadas y no recuerda cuáles +pixeles representa. La única forma de mover una figura en un canvas es limpíando +el canvas (o la parte del canvas alrededor de la figura) y redibujarlo +con la figura en una nueva posición. + +## SVG + +This book will not go into ((SVG)) in detail, but I will briefly +explain how it works. At the [end of the +chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs +that you must consider when deciding which ((drawing)) mechanism is +appropriate for a given application. + +This is an HTML document with a simple SVG ((picture)) in it: + +```{lang: "text/html", sandbox: "svg"} +

Normal HTML here.

+ + + + +``` + +{{index "circle (SVG tag)", "rect (SVG tag)", "XML namespace", XML, "xmlns attribute"}} + +The `xmlns` attribute changes an element (and its children) to a +different _XML namespace_. This namespace, identified by a ((URL)), +specifies the dialect that we are currently speaking. The `` +and `` tags, which do not exist in HTML, do have a meaning in +SVG—they draw shapes using the style and position specified by their +attributes. + +{{if book + +The document is displayed like this: + +{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} + +if}} + +{{index [DOM, graphics]}} + +These tags create DOM elements, just like HTML tags, that +scripts can interact with. For example, this changes the `` +element to be ((color))ed cyan instead: + +```{sandbox: "svg"} +let circle = document.querySelector("circle"); +circle.setAttribute("fill", "cyan"); +``` + +## The canvas element + +{{index [canvas, size], "canvas (HTML tag)"}} + +Canvas ((graphics)) can be drawn onto a `` element. You can +give such an element `width` and `height` attributes to determine its +size in ((pixel))s. + +A new canvas is empty, meaning it is entirely ((transparent)) and thus +shows up as empty space in the document. + +{{index "2d (canvas context)", "webgl (canvas context)", OpenGL, [canvas, context], dimensions, [interface, canvas]}} + +The `` tag is intended to allow different styles of +((drawing)). To get access to an actual drawing interface, we +first need to create a _((context))_, an object whose methods provide +the drawing interface. There are currently two widely supported +drawing styles: `"2d"` for two-dimensional graphics and `"webgl"` for +three-dimensional graphics through the OpenGL interface. + +{{index rendering, graphics, efficiency}} + +This book won't discuss WebGL—we'll stick to two dimensions. But if +you are interested in three-dimensional graphics, I do encourage you +to look into WebGL. It provides a direct interface to graphics +hardware and allows you to render even complicated scenes efficiently, +using JavaScript. + +{{index "getContext method", [canvas, context]}} + +You create a ((context)) with the `getContext` method on the +`` DOM element. + +```{lang: "text/html"} +

Before canvas.

+ +

After canvas.

+ +``` + +After creating the context object, the example draws a red +((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left +corner at coordinates (10,10). + +{{if book + +{{figure {url: "img/canvas_fill.png", alt: "A canvas with a rectangle",width: "2.5cm"}}} + +if}} + +{{index SVG, coordinates}} + +Just like in HTML (and SVG), the coordinate system that the canvas +uses puts (0,0) at the top-left corner, and the positive y-((axis)) +goes down from there. So (10,10) is 10 pixels below and to the right +of the top-left corner. + +{{id fill_stroke}} + +## Lines and surfaces + +{{index filling, stroking, drawing, SVG}} + +In the ((canvas)) interface, a shape can be _filled_, meaning its area +is given a certain color or pattern, or it can be _stroked_, which +means a ((line)) is drawn along its edge. The same terminology is used +by SVG. + +{{index "fillRect method", "strokeRect method"}} + +The `fillRect` method fills a ((rectangle)). It takes first the x- and +y-((coordinates)) of the rectangle's top-left corner, then its width, +and then its height. A similar method, `strokeRect`, draws the +((outline)) of a rectangle. + +{{index [state, "of canvas"]}} + +Neither method takes any further parameters. The color of the fill, +thickness of the stroke, and so on, are not determined by an argument +to the method (as you might reasonably expect) but rather by +properties of the context object. + +{{index filling, "fillStyle property"}} + +The `fillStyle` property controls the way shapes are filled. It can be +set to a string that specifies a ((color)), using the color notation +used by ((CSS)). + +{{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} + +The `strokeStyle` property works similarly but determines the color +used for a stroked line. The width of that line is determined by the +`lineWidth` property, which may contain any positive number. + +```{lang: "text/html"} + + +``` + +{{if book + +This code draws two blue squares, using a thicker line for the second +one. + +{{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} + +if}} + +{{index "default value", [canvas, size]}} + +When no `width` or `height` attribute is specified, as in the example, +a canvas element gets a default width of 300 pixels and height of 150 +pixels. + +## Paths + +{{index [path, canvas], [interface, design], [canvas, path]}} + +A path is a sequence of ((line))s. The 2D canvas interface takes a +peculiar approach to describing such a path. It is done entirely +through ((side effect))s. Paths are not values that can be stored and +passed around. Instead, if you want to do something with a path, you +make a sequence of method calls to describe its shape. + +```{lang: "text/html"} + + +``` + +{{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} + +This example creates a path with a number of horizontal ((line)) +segments and then strokes it using the `stroke` method. Each segment +created with `lineTo` starts at the path's _current_ position. That +position is usually the end of the last segment, unless `moveTo` was +called. In that case, the next segment would start at the position +passed to `moveTo`. + +{{if book + +The path described by the previous program looks like this: + +{{figure {url: "img/canvas_path.png", alt: "Stroking a number of lines",width: "2.1cm"}}} + +if}} + +{{index [path, canvas], filling, [path, closing], "fill method"}} + +When filling a path (using the `fill` method), each ((shape)) is +filled separately. A path can contain multiple shapes—each `moveTo` +motion starts a new one. But the path needs to be _closed_ (meaning +its start and end are in the same position) before it can be filled. +If the path is not already closed, a line is added from its end to its +start, and the shape enclosed by the completed path is filled. + +```{lang: "text/html"} + + +``` + +This example draws a filled triangle. Note that only two of the +triangle's sides are explicitly drawn. The third, from the +bottom-right corner back to the top, is implied and wouldn't be there +when you stroke the path. + +{{if book + +{{figure {url: "img/canvas_triangle.png", alt: "Filling a path",width: "2.2cm"}}} + +if}} + +{{index "stroke method", "closePath method", [path, closing], canvas}} + +You could also use the `closePath` method to explicitly close a path +by adding an actual ((line)) segment back to the path's start. This +segment _is_ drawn when stroking the path. + +## Curves + +{{index [path, canvas], canvas, drawing}} + +A path may also contain ((curve))d ((line))s. These are unfortunately +a bit more involved to draw. + +{{index "quadraticCurveTo method"}} + +The `quadraticCurveTo` method draws a curve to a given point. To +determine the curvature of the line, the method is given a ((control +point)) as well as a destination point. Imagine this control point as +_attracting_ the line, giving it its curve. The line won't go through +the control point, but its direction at the start and end points will +be such that a straight line in that direction would point toward the +control point. The following example illustrates this: + +```{lang: "text/html"} + + +``` + +{{if book + +It produces a path that looks like this: + +{{figure {url: "img/canvas_quadraticcurve.png", alt: "A quadratic curve",width: "2.3cm"}}} + +if}} + +{{index "stroke method"}} + +We draw a ((quadratic curve)) from the left to the right, with (60,10) +as control point, and then draw two ((line)) segments going through +that control point and back to the start of the line. The result +somewhat resembles a _((Star Trek))_ insignia. You can see the effect +of the control point: the lines leaving the lower corners start off in +the direction of the control point and then ((curve)) toward their +target. + +{{index canvas, "bezierCurveTo method"}} + +The `bezierCurveTo` method draws a similar kind of curve. Instead of a +single ((control point)), this one has two—one for each of the +((line))'s endpoints. Here is a similar sketch to illustrate the +behavior of such a curve: + +```{lang: "text/html"} + + +``` + +The two control points specify the direction at both ends of the +curve. The farther they are away from their corresponding point, the +more the curve will "bulge" in that direction. + +{{if book + +{{figure {url: "img/canvas_beziercurve.png", alt: "A bezier curve",width: "2.2cm"}}} + +if}} + +{{index "trial and error"}} + +Such ((curve))s can be hard to work with—it's not always clear how to +find the ((control point))s that provide the ((shape)) you are looking +for. Sometimes you can compute them, and sometimes you'll just have to +find a suitable value by trial and error. + +{{index "arc method", arc}} + +The `arc` method is a way to draw a line that curves along the edge of +a circle. It takes a pair of ((coordinates)) for the arc's center, a +radius, and then a start angle and end angle. + +{{index pi, "Math.PI constant"}} + +Those last two parameters make it possible to draw only part of the +circle. The ((angle))s are measured in ((radian))s, not ((degree))s. +This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, +which is about 6.28. The angle starts counting at the point to the +right of the circle's center and goes clockwise from there. You can +use a start of 0 and an end bigger than 2π (say, 7) to draw a full +circle. + +```{lang: "text/html"} + + +``` + +{{index "moveTo method", "arc method", [path, " canvas"]}} + +The resulting picture contains a ((line)) from the right of the full +circle (first call to `arc`) to the right of the quarter-((circle)) +(second call). Like other path-drawing methods, a line drawn with +`arc` is connected to the previous path segment. You can call `moveTo` +or start a new path to avoid this. + +{{if book + +{{figure {url: "img/canvas_circle.png", alt: "Drawing a circle",width: "4.9cm"}}} + +if}} + +{{id pie_chart}} + +## Drawing a pie chart + +{{index "pie chart example"}} + +Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your +first assignment is to draw a pie chart of its customer satisfaction +((survey)) results. + +The `results` binding contains an array of objects that represent the +survey responses. + +```{sandbox: "pie", includeCode: true} +const results = [ + {name: "Satisfied", count: 1043, color: "lightblue"}, + {name: "Neutral", count: 563, color: "lightgreen"}, + {name: "Unsatisfied", count: 510, color: "pink"}, + {name: "No comment", count: 175, color: "silver"} +]; +``` + +{{index "pie chart example"}} + +To draw a pie chart, we draw a number of pie slices, each made up of +an ((arc)) and a pair of ((line))s to the center of that arc. We can +compute the ((angle)) taken up by each arc by dividing a full circle +(2π) by the total number of responses and then multiplying that number +(the angle per response) by the number of people who picked a given +choice. + +```{lang: "text/html", sandbox: "pie"} + + +``` + +{{if book + +This draws the following chart: + +{{figure {url: "img/canvas_pie_chart.png", alt: "A pie chart",width: "5cm"}}} + +if}} + +But a chart that doesn't tell us what the slices mean isn't very +helpful. We need a way to draw text to the ((canvas)). + +## Text + +{{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} + +A 2D canvas drawing context provides the methods `fillText` and +`strokeText`. The latter can be useful for outlining letters, but +usually `fillText` is what you need. It will fill the outline of the +given ((text)) with the current `fillStyle`. + +```{lang: "text/html"} + + +``` + +You can specify the size, style, and ((font)) of the text with the +`font` property. This example just gives a font size and family name. +It is also possible to add `italic` or `bold` to the start of the +string to select a style. + +{{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} + +The last two arguments to `fillText` and `strokeText` provide the +position at which the font is drawn. By default, they indicate the +position of the start of the text's alphabetic baseline, which is the +line that letters "stand" on, not counting hanging parts in letters +such as _j_ or _p_. You can change the horizontal position by setting the +`textAlign` property to `"end"` or `"center"` and the vertical +position by setting `textBaseline` to `"top"`, `"middle"`, or +`"bottom"`. + +{{index "pie chart example"}} + +We'll come back to our pie chart, and the problem of ((label))ing the +slices, in the [exercises](canvas#exercise_pie_chart) at the end of +the chapter. + +## Images + +{{index "vector graphics", "bitmap graphics"}} + +In computer ((graphics)), a distinction is often made between _vector_ +graphics and _bitmap_ graphics. The first is what we have been doing +so far in this chapter—specifying a picture by giving a logical +description of ((shape))s. Bitmap graphics, on the other hand, don't +specify actual shapes but rather work with ((pixel)) data (rasters of +colored dots). + +{{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} + +The `drawImage` method allows us to draw ((pixel)) data onto a +((canvas)). This pixel data can originate from an `` element or +from another canvas. The following example creates a detached `` +element and loads an image file into it. But it cannot immediately +start drawing from this picture because the browser may not have +loaded it yet. To deal with this, we register a `"load"` event handler +and do the drawing after the image has loaded. + +```{lang: "text/html"} + + +``` + +{{index "drawImage method", scaling}} + +By default, `drawImage` will draw the image at its original size. You +can also give it two additional arguments to set a different width +and height. + +When `drawImage` is given _nine_ arguments, it can be used to draw +only a fragment of an image. The second through fifth arguments +indicate the rectangle (x, y, width, and height) in the source image +that should be copied, and the sixth to ninth arguments give the +rectangle (on the canvas) into which it should be copied. + +{{index "player", "pixel art"}} + +This can be used to pack multiple _((sprite))s_ (image elements) into +a single image file and then draw only the part you need. For example, +we have this picture containing a game character in multiple +((pose))s: + +{{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} + +{{index [animation, "platform game"]}} + +By alternating which pose we draw, we can show an animation that +looks like a walking character. + +{{index "fillRect method", "clearRect method", clearing}} + +To animate a ((picture)) on a ((canvas)), the `clearRect` method is +useful. It resembles `fillRect`, but instead of coloring the +rectangle, it makes it ((transparent)), removing the previously drawn +pixels. + +{{index "setInterval function", "img (HTML tag)"}} + +We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide +and 30 pixels high. The following code loads the image and then sets +up an interval (repeated timer) to draw the next ((frame)): + +```{lang: "text/html"} + + +``` + +{{index "remainder operator", "% operator", [animation, "platform game"]}} + +The `cycle` binding tracks our position in the animation. For each +((frame)), it is incremented and then clipped back to the 0 to 7 range +by using the remainder operator. This binding is then used to compute +the x-coordinate that the sprite for the current pose has in the +picture. + +## Transformation + +{{index transformation, mirroring}} + +{{indexsee flipping, mirroring}} + +But what if we want our character to walk to the left instead of to +the right? We could draw another set of sprites, of course. But we can +also instruct the ((canvas)) to draw the picture the other way round. + +{{index "scale method", scaling}} + +Calling the `scale` method will cause anything drawn after it to be +scaled. This method takes two parameters, one to set a horizontal +scale and one to set a vertical scale. + +```{lang: "text/html"} + + +``` + +{{if book + +Because of the call to `scale`, the circle is drawn three times as wide +and half as high. + +{{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} + +if}} + +{{index mirroring}} + +Scaling will cause everything about the drawn image, including the +((line width)), to be stretched out or squeezed together as specified. +Scaling by a negative amount will flip the picture around. The +flipping happens around point (0,0), which means it will also flip the +direction of the coordinate system. When a horizontal scaling of -1 is +applied, a shape drawn at x position 100 will end up at what used to +be position -100. + +{{index "drawImage method"}} + +So to turn a picture around, we can't simply add `cx.scale(-1, 1)` +before the call to `drawImage` because that would move our picture +outside of the ((canvas)), where it won't be visible. You could adjust +the ((coordinates)) given to `drawImage` to compensate for this by +drawing the image at x position -50 instead of 0. Another solution, +which doesn't require the code that does the drawing to know about the +scale change, is to adjust the ((axis)) around which the scaling +happens. + +{{index "rotate method", "translate method", transformation}} + +There are several other methods besides `scale` that influence the +coordinate system for a ((canvas)). You can rotate subsequently drawn +shapes with the `rotate` method and move them with the `translate` +method. The interesting—and confusing—thing is that these +transformations _stack_, meaning that each one happens relative to the +previous transformations. + +{{index "rotate method", "translate method"}} + +So if we translate by 10 horizontal pixels twice, everything will be +drawn 20 pixels to the right. If we first move the center of the +coordinate system to (50,50) and then rotate by 20 ((degree))s (about +0.1π ((radian))s), that rotation will happen _around_ point (50,50). + +{{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} + +{{index coordinates}} + +But if we _first_ rotate by 20 degrees and _then_ translate by +(50,50), the translation will happen in the rotated coordinate system +and thus produce a different orientation. The order in which +transformations are applied matters. + +{{index axis, mirroring}} + +To flip a picture around the vertical line at a given x position, we +can do the following: + +```{includeCode: true} +function flipHorizontally(context, around) { + context.translate(around, 0); + context.scale(-1, 1); + context.translate(-around, 0); +} +``` + +{{index "flipHorizontally method"}} + +We move the y-((axis)) to where we want our ((mirror)) to be, apply +the mirroring, and finally move the y-axis back to its proper place in +the mirrored universe. The following picture explains why this works: + +{{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} + +{{index "translate method", "scale method", transformation, canvas}} + +This shows the coordinate systems before and after mirroring across +the central line. The triangles are numbered to illustrate each step. +If we draw a triangle at a positive x position, it would, by default, +be in the place where triangle 1 is. A call to `flipHorizontally` +first does a translation to the right, which gets us to triangle 2. It +then scales, flipping the triangle over to position 3. This is not +where it should be, if it were mirrored in the given line. The second +`translate` call fixes this—it "cancels" the initial translation and +makes triangle 4 appear exactly where it should. + +We can now draw a mirrored character at position (100,0) by flipping +the world around the character's vertical center. + +```{lang: "text/html"} + + +``` + +## Storing and clearing transformations + +{{index "side effect", canvas, transformation}} + +Transformations stick around. Everything else we draw after +((drawing)) that mirrored character would also be mirrored. That might +be inconvenient. + +It is possible to save the current transformation, do some drawing and +transforming, and then restore the old transformation. This is usually +the proper thing to do for a function that needs to temporarily +transform the coordinate system. First, we save whatever +transformation the code that called the function was using. Then the +function does its thing, adding more transformations on top of the +current transformation. Finally, we revert to the +transformation we started with. + +{{index "save method", "restore method", [state, "of canvas"]}} + +The `save` and `restore` methods on the 2D ((canvas)) context do this +((transformation)) management. They conceptually keep a stack of +transformation states. When you call `save`, the current state is +pushed onto the stack, and when you call `restore`, the state on top +of the stack is taken off and used as the context's current +transformation. You can also call `resetTransform` to fully reset the +transformation. + +{{index "branching recursion", "fractal example", recursion}} + +The `branch` function in the following example illustrates what you +can do with a function that changes the transformation and then calls +a function (in this case itself), which continues drawing with +the given transformation. + +This function draws a treelike shape by drawing a line, moving the +center of the coordinate system to the end of the line, and calling +itself twice—first rotated to the left and then rotated to the right. +Every call reduces the length of the branch drawn, and the recursion +stops when the length drops below 8. + +```{lang: "text/html"} + + +``` + +{{if book + +The result is a simple fractal. + +{{figure {url: "img/canvas_tree.png", alt: "A recursive picture",width: "5cm"}}} + +if}} + +{{index "save method", "restore method", canvas, "rotate method"}} + +If the calls to `save` and `restore` were not there, the second +recursive call to `branch` would end up with the position and rotation +created by the first call. It wouldn't be connected to the current +branch but rather to the innermost, rightmost branch drawn by the +first call. The resulting shape might also be interesting, but it is +definitely not a tree. + +{{id canvasdisplay}} + +## Back to the game + +{{index "drawImage method"}} + +We now know enough about ((canvas)) drawing to start working on a +((canvas))-based ((display)) system for the ((game)) from the +[previous chapter](game). The new display will no longer be showing +just colored boxes. Instead, we'll use `drawImage` to draw pictures +that represent the game's elements. + +{{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} + +We define another display object type called `CanvasDisplay`, +supporting the same interface as `DOMDisplay` from [Chapter +?](game#domdisplay), namely, the methods `syncState` and `clear`. + +{{index [state, "in objects"]}} + +This object keeps a little more information than `DOMDisplay`. Rather +than using the scroll position of its DOM element, it tracks its own +((viewport)), which tells us what part of the level we are currently +looking at. Finally, it keeps a `flipPlayer` property so that even +when the player is standing still, it keeps facing the direction it +last moved in. + +```{sandbox: "game", includeCode: true} +class CanvasDisplay { + constructor(parent, level) { + this.canvas = document.createElement("canvas"); + this.canvas.width = Math.min(600, level.width * scale); + this.canvas.height = Math.min(450, level.height * scale); + parent.appendChild(this.canvas); + this.cx = this.canvas.getContext("2d"); + + this.flipPlayer = false; + + this.viewport = { + left: 0, + top: 0, + width: this.canvas.width / scale, + height: this.canvas.height / scale + }; + } + + clear() { + this.canvas.remove(); + } +} +``` + +The `syncState` method first computes a new viewport and then draws +the game scene at the appropriate position. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.syncState = function(state) { + this.updateViewport(state); + this.clearDisplay(state.status); + this.drawBackground(state.level); + this.drawActors(state.actors); +}; +``` + +{{index scrolling, clearing}} + +Contrary to `DOMDisplay`, this display style _does_ have to redraw the +background on every update. Because shapes on a canvas are just +((pixel))s, after we draw them there is no good way to move them (or +remove them). The only way to update the canvas display is to clear it +and redraw the scene. We may also have scrolled, which requires the +background to be in a different position. + +{{index "CanvasDisplay class"}} + +The `updateViewport` method is similar to `DOMDisplay`'s +`scrollPlayerIntoView` method. It checks whether the player is too +close to the edge of the screen and moves the ((viewport)) when this +is the case. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.updateViewport = function(state) { + let view = this.viewport, margin = view.width / 3; + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)); + + if (center.x < view.left + margin) { + view.left = Math.max(center.x - margin, 0); + } else if (center.x > view.left + view.width - margin) { + view.left = Math.min(center.x + margin - view.width, + state.level.width - view.width); + } + if (center.y < view.top + margin) { + view.top = Math.max(center.y - margin, 0); + } else if (center.y > view.top + view.height - margin) { + view.top = Math.min(center.y + margin - view.height, + state.level.height - view.height); + } +}; +``` + +{{index boundary, "Math.max function", "Math.min function", clipping}} + +The calls to `Math.max` and `Math.min` ensure that the viewport does +not end up showing space outside of the level. `Math.max(x, 0)` makes +sure the resulting number is not less than zero. `Math.min` +similarly guarantees that a value stays below a given bound. + +When ((clearing)) the display, we'll use a slightly different +((color)) depending on whether the game is won (brighter) or lost +(darker). + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.clearDisplay = function(status) { + if (status == "won") { + this.cx.fillStyle = "rgb(68, 191, 255)"; + } else if (status == "lost") { + this.cx.fillStyle = "rgb(44, 136, 214)"; + } else { + this.cx.fillStyle = "rgb(52, 166, 251)"; + } + this.cx.fillRect(0, 0, + this.canvas.width, this.canvas.height); +}; +``` + +{{index "Math.floor function", "Math.ceil function", rounding}} + +To draw the background, we run through the tiles that are visible in +the current viewport, using the same trick used in the `touches` +method from the [previous chapter](game#touches). + +```{sandbox: "game", includeCode: true} +let otherSprites = document.createElement("img"); +otherSprites.src = "img/sprites.png"; + +CanvasDisplay.prototype.drawBackground = function(level) { + let {left, top, width, height} = this.viewport; + let xStart = Math.floor(left); + let xEnd = Math.ceil(left + width); + let yStart = Math.floor(top); + let yEnd = Math.ceil(top + height); + + for (let y = yStart; y < yEnd; y++) { + for (let x = xStart; x < xEnd; x++) { + let tile = level.rows[y][x]; + if (tile == "empty") continue; + let screenX = (x - left) * scale; + let screenY = (y - top) * scale; + let tileX = tile == "lava" ? scale : 0; + this.cx.drawImage(otherSprites, + tileX, 0, scale, scale, + screenX, screenY, scale, scale); + } + } +}; +``` + +{{index "drawImage method", sprite, tile}} + +Tiles that are not empty are drawn with `drawImage`. The +`otherSprites` image contains the pictures used for elements other +than the player. It contains, from left to right, the wall tile, the +lava tile, and the sprite for a coin. + +{{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} + +{{index scaling}} + +Background tiles are 20 by 20 pixels since we will use the same scale +that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 +(the value of the `scale` binding), and the offset for walls is 0. + +{{index drawing, "load event", "drawImage method"}} + +We don't bother waiting for the sprite image to load. Calling +`drawImage` with an image that hasn't been loaded yet will simply do +nothing. Thus, we might fail to draw the game properly for the first +few ((frame))s, while the image is still loading, but that is not a +serious problem. Since we keep updating the screen, the correct scene +will appear as soon as the loading finishes. + +{{index "player", [animation, "platform game"], drawing}} + +The ((walking)) character shown earlier will be used to represent the +player. The code that draws it needs to pick the right ((sprite)) and +direction based on the player's current motion. The first eight +sprites contain a walking animation. When the player is moving along a +floor, we cycle through them based on the current time. We want to +switch frames every 60 milliseconds, so the ((time)) is divided by 60 +first. When the player is standing still, we draw the ninth sprite. +During jumps, which are recognized by the fact that the vertical speed +is not zero, we use the tenth, rightmost sprite. + +{{index "flipHorizontally function", "CanvasDisplay class"}} + +Because the ((sprite))s are slightly wider than the player object—24 +instead of 16 pixels to allow some space for feet and arms—the method +has to adjust the x-coordinate and width by a given amount +(`playerXOverlap`). + +```{sandbox: "game", includeCode: true} +let playerSprites = document.createElement("img"); +playerSprites.src = "img/player.png"; +const playerXOverlap = 4; + +CanvasDisplay.prototype.drawPlayer = function(player, x, y, + width, height){ + width += playerXOverlap * 2; + x -= playerXOverlap; + if (player.speed.x != 0) { + this.flipPlayer = player.speed.x < 0; + } + + let tile = 8; + if (player.speed.y != 0) { + tile = 9; + } else if (player.speed.x != 0) { + tile = Math.floor(Date.now() / 60) % 8; + } + + this.cx.save(); + if (this.flipPlayer) { + flipHorizontally(this.cx, x + width / 2); + } + let tileX = tile * width; + this.cx.drawImage(playerSprites, tileX, 0, width, height, + x, y, width, height); + this.cx.restore(); +}; +``` + +The `drawPlayer` method is called by `drawActors`, which is +responsible for drawing all the actors in the game. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.drawActors = function(actors) { + for (let actor of actors) { + let width = actor.size.x * scale; + let height = actor.size.y * scale; + let x = (actor.pos.x - this.viewport.left) * scale; + let y = (actor.pos.y - this.viewport.top) * scale; + if (actor.type == "player") { + this.drawPlayer(actor, x, y, width, height); + } else { + let tileX = (actor.type == "coin" ? 2 : 1) * scale; + this.cx.drawImage(otherSprites, + tileX, 0, width, height, + x, y, width, height); + } + } +}; +``` + +When ((drawing)) something that is not the ((player)), we look at its +type to find the offset of the correct sprite. The ((lava)) tile is +found at offset 20, and the ((coin)) sprite is found at 40 (two times +`scale`). + +{{index viewport}} + +We have to subtract the viewport's position when computing the actor's +position since (0,0) on our ((canvas)) corresponds to the top left of +the viewport, not the top left of the level. We could also have used +`translate` for this. Either way works. + +{{if interactive + +This document plugs the new display into `runGame`: + +```{lang: "text/html", sandbox: game, focus: yes, startCode: true} + + + +``` + +if}} + +{{if book + +{{index [game, screenshot], [game, "with canvas"]}} + +That concludes the new ((display)) system. The resulting game looks +something like this: + +{{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} + +if}} + +{{id graphics_tradeoffs}} + +## Choosing a graphics interface + +So when you need to generate graphics in the browser, you can choose +between plain HTML, ((SVG)), and ((canvas)). There is no single +_best_ approach that works in all situations. Each option has +strengths and weaknesses. + +{{index "text wrapping"}} + +Plain HTML has the advantage of being simple. It also integrates well +with ((text)). Both SVG and canvas allow you to draw text, but they +won't help you position that text or wrap it when it takes up more +than one line. In an HTML-based picture, it is much easier to include +blocks of text. + +{{index zooming, SVG}} + +SVG can be used to produce ((crisp)) ((graphics)) that look good at +any zoom level. Unlike HTML, it is designed for drawing +and is thus more suitable for that purpose. + +{{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} + +Both SVG and HTML build up a data structure (the DOM) that +represents your picture. This makes it possible to modify elements +after they are drawn. If you need to repeatedly change a small part of +a big ((picture)) in response to what the user is doing or as part of +an ((animation)), doing it in a canvas can be needlessly expensive. +The DOM also allows us to register mouse event handlers on every +element in the picture (even on shapes drawn with SVG). You can't do +that with canvas. + +{{index performance, optimization}} + +But ((canvas))'s ((pixel))-oriented approach can be an advantage when +drawing a huge number of tiny elements. The fact that it does not +build up a data structure but only repeatedly draws onto the same +pixel surface gives canvas a lower cost per shape. + +{{index "ray tracer"}} + +There are also effects, such as rendering a scene one pixel at a time +(for example, using a ray tracer) or postprocessing an image with +JavaScript (blurring or distorting it), that can be realistically +handled only by a ((pixel))-based approach. + +In some cases, you may want to combine several of these techniques. +For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but +show ((text))ual information by positioning an HTML element on top +of the picture. + +{{index display}} + +For nondemanding applications, it really doesn't matter much which +interface you choose. The display we built for our game in this +chapter could have been implemented using any of these three +((graphics)) technologies since it does not need to draw text, handle +mouse interaction, or work with an extraordinarily large number of +elements. + +## Summary + +In this chapter we discussed techniques for drawing graphics in the +browser, focusing on the `` element. + +A canvas node represents an area in a document that our program may +draw on. This drawing is done through a drawing context object, +created with the `getContext` method. + +The 2D drawing interface allows us to fill and stroke various shapes. +The context's `fillStyle` property determines how shapes are filled. +The `strokeStyle` and `lineWidth` properties control the way lines are +drawn. + +Rectangles and pieces of text can be drawn with a single method call. +The `fillRect` and `strokeRect` methods draw rectangles, and the +`fillText` and `strokeText` methods draw text. To create custom +shapes, we must first build up a path. + +{{index stroking, filling}} + +Calling `beginPath` starts a new path. A number of other methods add +lines and curves to the current path. For example, `lineTo` can add a +straight line. When a path is finished, it can be filled with the +`fill` method or stroked with the `stroke` method. + +Moving pixels from an image or another canvas onto our canvas is done +with the `drawImage` method. By default, this method draws the whole +source image, but by giving it more parameters, you can copy a +specific area of the image. We used this for our game by copying +individual poses of the game character out of an image that contained +many such poses. + +Transformations allow you to draw a shape in multiple orientations. A +2D drawing context has a current transformation that can be changed +with the `translate`, `scale`, and `rotate` methods. These will affect +all subsequent drawing operations. A transformation state can be saved +with the `save` method and restored with the `restore` method. + +When showing an animation on a canvas, the `clearRect` method can be +used to clear part of the canvas before redrawing it. + +## Exercises + +### Shapes + +{{index "shapes (exercise)"}} + +Write a program that draws the following ((shape))s on a ((canvas)): + +{{index rotation}} + +1. A ((trapezoid)) (a ((rectangle)) that is wider on one side) + +2. A red ((diamond)) (a rectangle rotated 45 degrees or ¼π radians) + +3. A zigzagging ((line)) + +4. A ((spiral)) made up of 100 straight line segments + +5. A yellow ((star)) + +{{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} + +When drawing the last two, you may want to refer to the explanation of +`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes +how to get coordinates on a circle using these functions. + +{{index readability, "hard-coding"}} + +I recommend creating a function for each shape. Pass the position, and +optionally other properties such as the size or the number of points, +as parameters. The alternative, which is to hard-code numbers all over +your code, tends to make the code needlessly hard to read and modify. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index [path, canvas], "shapes (exercise)"}} + +The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable +center coordinates and add each of the four corners around the center. + +{{index "flipHorizontally function", rotation}} + +The ((diamond)) (2) can be drawn the straightforward way, with a path, +or the interesting way, with a `rotate` ((transformation)). To use +rotation, you will have to apply a trick similar to what we did in the +`flipHorizontally` function. Because you want to rotate around the +center of your rectangle and not around the point (0,0), you must +first `translate` to there, then rotate, and then translate back. + +Make sure you reset the transformation after drawing any shape that +creates one. + +{{index "remainder operator", "% operator"}} + +For the ((zigzag)) (3) it becomes impractical to write a new call to +`lineTo` for each line segment. Instead, you should use a ((loop)). +You can have each iteration draw either two ((line)) segments (right +and then left again) or one, in which case you must use the evenness +(`% 2`) of the loop index to determine whether to go left or right. + +You'll also need a loop for the ((spiral)) (4). If you draw a series +of points, with each point moving further along a circle around the +spiral's center, you get a circle. If, during the loop, you vary the +radius of the circle on which you are putting the current point and go +around more than once, the result is a spiral. + +{{index "quadraticCurveTo method"}} + +The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. +You could also draw one with straight lines. Divide a circle into +eight pieces for a star with eight points, or however many pieces you +want. Draw lines between these points, making them curve toward the +center of the star. With `quadraticCurveTo`, you can use the center as +the control point. + +hint}} + +{{id exercise_pie_chart}} + +### The pie chart + +{{index label, text, "pie chart example"}} + +[Earlier](canvas#pie_chart) in the chapter, we saw an example program +that drew a pie chart. Modify this program so that the name of each +category is shown next to the slice that represents it. Try to find a +pleasing-looking way to automatically position this text that would +work for other data sets as well. You may assume that categories are +big enough to leave ample room for their labels. + +You might need `Math.sin` and `Math.cos` again, which are described in +[Chapter ?](dom#sin_cos). + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} + +You will need to call `fillText` and set the context's `textAlign` and +`textBaseline` properties in such a way that the text ends up where +you want it. + +A sensible way to position the labels would be to put the text on the +line going from the center of the pie through the middle of the slice. +You don't want to put the text directly against the side of the pie +but rather move the text out to the side of the pie by a given number +of pixels. + +The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The +following code finds a position on this line 120 pixels from the +center: + +```{test: no} +let middleAngle = currentAngle + 0.5 * sliceAngle; +let textX = Math.cos(middleAngle) * 120 + centerX; +let textY = Math.sin(middleAngle) * 120 + centerY; +``` + +For `textBaseline`, the value `"middle"` is probably appropriate when +using this approach. What to use for `textAlign` depends on which side +of the circle we are on. On the left, it should be `"right"`, and on +the right, it should be `"left"`, so that the text is positioned away +from the pie. + +{{index "Math.cos function"}} + +If you are not sure how to find out which side of the circle a given +angle is on, look to the explanation of `Math.cos` in [Chapter +?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it +corresponds to, which in turn tells us exactly which side of the +circle we are on. + +hint}} + +### A bouncing ball + +{{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} + +Use the `requestAnimationFrame` technique that we saw in [Chapter +?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a +((box)) with a bouncing ((ball)) in it. The ball moves at a constant +((speed)) and bounces off the box's sides when it hits them. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "strokeRect method", animation, "arc method"}} + +A ((box)) is easy to draw with `strokeRect`. Define a binding that +holds its size or define two bindings if your box's width and height +differ. To create a round ((ball)), start a path and call `arc(x, y, +radius, 0, 7)`, which creates an arc going from zero to more than a +whole circle. Then fill the path. + +{{index "collision detection", "Vec class"}} + +To model the ball's position and ((speed)), you can use the `Vec` +class from [Chapter ?](game#vector)[ (which is available on this +page)]{if interactive}. Give it a starting speed, preferably one that +is not purely vertical or horizontal, and for every ((frame)) multiply +that speed by the amount of time that elapsed. When the ball gets +too close to a vertical wall, invert the x component in its speed. +Likewise, invert the y component when it hits a horizontal wall. + +{{index "clearRect method", clearing}} + +After finding the ball's new position and speed, use `clearRect` to +delete the scene and redraw it using the new position. + +hint}} + +### Precomputed mirroring + +{{index optimization, "bitmap graphics", mirror}} + +One unfortunate thing about ((transformation))s is that they slow down +the drawing of bitmaps. The position and size of each ((pixel)) has to be +transformed, and though it is possible that ((browser))s will get +cleverer about transformation in the ((future)), they currently cause a +measurable increase in the time it takes to draw a bitmap. + +In a game like ours, where we are drawing only a single transformed +sprite, this is a nonissue. But imagine that we need to draw hundreds +of characters or thousands of rotating particles from an explosion. + +Think of a way to allow us to draw an inverted character without +loading additional image files and without having to make transformed +`drawImage` calls every frame. + +{{hint + +{{index mirror, scaling, "drawImage method"}} + +The key to the solution is the fact that we can use a ((canvas)) +element as a source image when using `drawImage`. It is possible to +create an extra `` element, without adding it to the document, +and draw our inverted sprites to it, once. When drawing an actual +frame, we just copy the already inverted sprites to the main canvas. + +{{index "load event"}} + +Some care would be required because images do not load instantly. We +do the inverted drawing only once, and if we do it before the image +loads, it won't draw anything. A `"load"` handler on the image can be +used to draw the inverted images to the extra canvas. This canvas can +be used as a drawing source immediately (it'll simply be blank until +we draw the character onto it). + +hint}} + From 8617cd104f9021ed7faed0c1b44f31c6c953a2af Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:36:02 -0500 Subject: [PATCH 04/22] =?UTF-8?q?Segunda=20secci=C3=B3n:=20SVG=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 024ef9ba5..4e17c6524 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -50,16 +50,15 @@ con la figura en una nueva posición. ## SVG -This book will not go into ((SVG)) in detail, but I will briefly -explain how it works. At the [end of the -chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs -that you must consider when deciding which ((drawing)) mechanism is -appropriate for a given application. +Este libro no hablará de ((SVG)) a detalle, pero explicaré +de forma breve como funciona. Al final de [final del capítulo](canvas#graphics_tradeoffs), regresaré a estos temas +que debes considerar cuando debas decidir cuál mecanismo de ((dibujo)) sea +apropiado dada una aplicación. -This is an HTML document with a simple SVG ((picture)) in it: +Este es un documento HTML con una ((imagen)) SVG simple: ```{lang: "text/html", sandbox: "svg"} -

Normal HTML here.

+

HTML normal aquí.

` -and `` tags, which do not exist in HTML, do have a meaning in -SVG—they draw shapes using the style and position specified by their -attributes. +El atributo `xmlns` cambia un elemento (y sus hijos) a un +_XML namespace_ diferente. Este _namespace_, identificado por una ((URL)), +especifica el dialecto que estamos usando. las etiquetas +`` y ``, —que no existen en HTML, pero tienen un significado en +SVG— dibujan formas usando el estilo y posición especificados por sus +atributos. {{if book -The document is displayed like this: +El documento muestra algo así: -{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} +{{figure {url: "img/svg-demo.png", alt: "Una imagen SVG embebida",width: "4.5cm"}}} if}} {{index [DOM, graphics]}} -These tags create DOM elements, just like HTML tags, that -scripts can interact with. For example, this changes the `` -element to be ((color))ed cyan instead: +Estas etiquetas crean elementos en el DOM, como etiquetas de HTML, con las +que los scripts pueden interactuar. Por ejemplo, el siguiente código cambia el elemento `` +para que sea ((color))eado de cyan: ```{sandbox: "svg"} -let circle = document.querySelector("circle"); -circle.setAttribute("fill", "cyan"); +let circulo = document.querySelector("circle"); +circulo.setAttribute("fill", "cyan"); ``` -## The canvas element +## El elemento canvas {{index [canvas, size], "canvas (HTML tag)"}} From 2da3fb4a226ab433287c5e780a47c4969ec2372d Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:12:23 -0500 Subject: [PATCH 05/22] =?UTF-8?q?Primera=20secci=C3=B3n=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 1520 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1520 insertions(+) create mode 100644 17_canvas.md diff --git a/17_canvas.md b/17_canvas.md new file mode 100644 index 000000000..024ef9ba5 --- /dev/null +++ b/17_canvas.md @@ -0,0 +1,1520 @@ +{{meta {load_files: ["code/chapter/16_game.js", "code/levels.js", "code/chapter/17_canvas.js"], zip: "html include=[\"img/player.png\", \"img/sprites.png\"]"}}} + +# Dibujando en Canvas + +{{quote {author: "M.C. Escher", title: "citado por Bruno Ernst en The Magic Mirror of M.C. Escher", chapter: true} + +El dibujo es decepción. + +quote}} + +{{index "Escher, M.C."}} + +{{figure {url: "img/chapter_picture_17.jpg", alt: "Imagen de un brazo robótico dibujando en un papel", chapter: "framed"}}} + +{{index CSS, "transform (CSS)", [DOM, graphics]}} + +Los navegadores nos proporcionan varias formas de mostrar((gráficos)). La más simple +es usar estilos para posiciones y colores de elementos regulares del DOM. +Esto puede ser tardado, como vimos en el [previous +chapter](juego) pasado. Agregando ((imagenes))s de fondo transparentes + a los nodos, podemos hacer que se vean exactamente de la form que +queremos. incluso es posible rotar o distorsionar nodos con el estilo `transform`. + +Pero estaríamos usando el DOM para algo para lo que +no fue diseñado. Algunas tareas, como dibujar una ((línea)) entre +puntos arbitrarios, son extremadamente incómodas de hacer con elementos HTML +regulares. + +{{index SVG, "img (HTML tag)"}} + +Tenemos dos alternativas. La primera es basada en el DOM, pero utiliza +_Scalable Vector Graphics_ (SVG), más que HTML. Piensa en SVG como un +dialecto de un ((documento)) de marcado que se centra en ((figura))s más que en +texto. Puedes embeber un documento SVG directamente en un archivo HTML o +puedes incluirlo con una etiqueta ``. + +{{index clearing, [DOM graphics], [interface, canvas]}} + +La segunda alternativa es llamada _((canvas))_. Un canvas es un +elemento del DOM que encapsula una ((imagen)). Proporciona una +intefaz de programción para dibujar ((forma))s en el espacio +del nodo. La principal diferencia entre un canvas y una imagen +SVG es que en SVG la descripción original de las figuras es +preservada de manera que puedan ser movidas o reescaladas en cualquier momento. Un canvas, +por otro lado, convierte las figuras a ((pixele))s (puntos de color en +una rejilla) tan pronto son dibujadas y no recuerda cuáles +pixeles representa. La única forma de mover una figura en un canvas es limpíando +el canvas (o la parte del canvas alrededor de la figura) y redibujarlo +con la figura en una nueva posición. + +## SVG + +This book will not go into ((SVG)) in detail, but I will briefly +explain how it works. At the [end of the +chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs +that you must consider when deciding which ((drawing)) mechanism is +appropriate for a given application. + +This is an HTML document with a simple SVG ((picture)) in it: + +```{lang: "text/html", sandbox: "svg"} +

Normal HTML here.

+ + + + +``` + +{{index "circle (SVG tag)", "rect (SVG tag)", "XML namespace", XML, "xmlns attribute"}} + +The `xmlns` attribute changes an element (and its children) to a +different _XML namespace_. This namespace, identified by a ((URL)), +specifies the dialect that we are currently speaking. The `` +and `` tags, which do not exist in HTML, do have a meaning in +SVG—they draw shapes using the style and position specified by their +attributes. + +{{if book + +The document is displayed like this: + +{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} + +if}} + +{{index [DOM, graphics]}} + +These tags create DOM elements, just like HTML tags, that +scripts can interact with. For example, this changes the `` +element to be ((color))ed cyan instead: + +```{sandbox: "svg"} +let circle = document.querySelector("circle"); +circle.setAttribute("fill", "cyan"); +``` + +## The canvas element + +{{index [canvas, size], "canvas (HTML tag)"}} + +Canvas ((graphics)) can be drawn onto a `` element. You can +give such an element `width` and `height` attributes to determine its +size in ((pixel))s. + +A new canvas is empty, meaning it is entirely ((transparent)) and thus +shows up as empty space in the document. + +{{index "2d (canvas context)", "webgl (canvas context)", OpenGL, [canvas, context], dimensions, [interface, canvas]}} + +The `` tag is intended to allow different styles of +((drawing)). To get access to an actual drawing interface, we +first need to create a _((context))_, an object whose methods provide +the drawing interface. There are currently two widely supported +drawing styles: `"2d"` for two-dimensional graphics and `"webgl"` for +three-dimensional graphics through the OpenGL interface. + +{{index rendering, graphics, efficiency}} + +This book won't discuss WebGL—we'll stick to two dimensions. But if +you are interested in three-dimensional graphics, I do encourage you +to look into WebGL. It provides a direct interface to graphics +hardware and allows you to render even complicated scenes efficiently, +using JavaScript. + +{{index "getContext method", [canvas, context]}} + +You create a ((context)) with the `getContext` method on the +`` DOM element. + +```{lang: "text/html"} +

Before canvas.

+ +

After canvas.

+ +``` + +After creating the context object, the example draws a red +((rectangle)) 100 ((pixel))s wide and 50 pixels high, with its top-left +corner at coordinates (10,10). + +{{if book + +{{figure {url: "img/canvas_fill.png", alt: "A canvas with a rectangle",width: "2.5cm"}}} + +if}} + +{{index SVG, coordinates}} + +Just like in HTML (and SVG), the coordinate system that the canvas +uses puts (0,0) at the top-left corner, and the positive y-((axis)) +goes down from there. So (10,10) is 10 pixels below and to the right +of the top-left corner. + +{{id fill_stroke}} + +## Lines and surfaces + +{{index filling, stroking, drawing, SVG}} + +In the ((canvas)) interface, a shape can be _filled_, meaning its area +is given a certain color or pattern, or it can be _stroked_, which +means a ((line)) is drawn along its edge. The same terminology is used +by SVG. + +{{index "fillRect method", "strokeRect method"}} + +The `fillRect` method fills a ((rectangle)). It takes first the x- and +y-((coordinates)) of the rectangle's top-left corner, then its width, +and then its height. A similar method, `strokeRect`, draws the +((outline)) of a rectangle. + +{{index [state, "of canvas"]}} + +Neither method takes any further parameters. The color of the fill, +thickness of the stroke, and so on, are not determined by an argument +to the method (as you might reasonably expect) but rather by +properties of the context object. + +{{index filling, "fillStyle property"}} + +The `fillStyle` property controls the way shapes are filled. It can be +set to a string that specifies a ((color)), using the color notation +used by ((CSS)). + +{{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} + +The `strokeStyle` property works similarly but determines the color +used for a stroked line. The width of that line is determined by the +`lineWidth` property, which may contain any positive number. + +```{lang: "text/html"} + + +``` + +{{if book + +This code draws two blue squares, using a thicker line for the second +one. + +{{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} + +if}} + +{{index "default value", [canvas, size]}} + +When no `width` or `height` attribute is specified, as in the example, +a canvas element gets a default width of 300 pixels and height of 150 +pixels. + +## Paths + +{{index [path, canvas], [interface, design], [canvas, path]}} + +A path is a sequence of ((line))s. The 2D canvas interface takes a +peculiar approach to describing such a path. It is done entirely +through ((side effect))s. Paths are not values that can be stored and +passed around. Instead, if you want to do something with a path, you +make a sequence of method calls to describe its shape. + +```{lang: "text/html"} + + +``` + +{{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} + +This example creates a path with a number of horizontal ((line)) +segments and then strokes it using the `stroke` method. Each segment +created with `lineTo` starts at the path's _current_ position. That +position is usually the end of the last segment, unless `moveTo` was +called. In that case, the next segment would start at the position +passed to `moveTo`. + +{{if book + +The path described by the previous program looks like this: + +{{figure {url: "img/canvas_path.png", alt: "Stroking a number of lines",width: "2.1cm"}}} + +if}} + +{{index [path, canvas], filling, [path, closing], "fill method"}} + +When filling a path (using the `fill` method), each ((shape)) is +filled separately. A path can contain multiple shapes—each `moveTo` +motion starts a new one. But the path needs to be _closed_ (meaning +its start and end are in the same position) before it can be filled. +If the path is not already closed, a line is added from its end to its +start, and the shape enclosed by the completed path is filled. + +```{lang: "text/html"} + + +``` + +This example draws a filled triangle. Note that only two of the +triangle's sides are explicitly drawn. The third, from the +bottom-right corner back to the top, is implied and wouldn't be there +when you stroke the path. + +{{if book + +{{figure {url: "img/canvas_triangle.png", alt: "Filling a path",width: "2.2cm"}}} + +if}} + +{{index "stroke method", "closePath method", [path, closing], canvas}} + +You could also use the `closePath` method to explicitly close a path +by adding an actual ((line)) segment back to the path's start. This +segment _is_ drawn when stroking the path. + +## Curves + +{{index [path, canvas], canvas, drawing}} + +A path may also contain ((curve))d ((line))s. These are unfortunately +a bit more involved to draw. + +{{index "quadraticCurveTo method"}} + +The `quadraticCurveTo` method draws a curve to a given point. To +determine the curvature of the line, the method is given a ((control +point)) as well as a destination point. Imagine this control point as +_attracting_ the line, giving it its curve. The line won't go through +the control point, but its direction at the start and end points will +be such that a straight line in that direction would point toward the +control point. The following example illustrates this: + +```{lang: "text/html"} + + +``` + +{{if book + +It produces a path that looks like this: + +{{figure {url: "img/canvas_quadraticcurve.png", alt: "A quadratic curve",width: "2.3cm"}}} + +if}} + +{{index "stroke method"}} + +We draw a ((quadratic curve)) from the left to the right, with (60,10) +as control point, and then draw two ((line)) segments going through +that control point and back to the start of the line. The result +somewhat resembles a _((Star Trek))_ insignia. You can see the effect +of the control point: the lines leaving the lower corners start off in +the direction of the control point and then ((curve)) toward their +target. + +{{index canvas, "bezierCurveTo method"}} + +The `bezierCurveTo` method draws a similar kind of curve. Instead of a +single ((control point)), this one has two—one for each of the +((line))'s endpoints. Here is a similar sketch to illustrate the +behavior of such a curve: + +```{lang: "text/html"} + + +``` + +The two control points specify the direction at both ends of the +curve. The farther they are away from their corresponding point, the +more the curve will "bulge" in that direction. + +{{if book + +{{figure {url: "img/canvas_beziercurve.png", alt: "A bezier curve",width: "2.2cm"}}} + +if}} + +{{index "trial and error"}} + +Such ((curve))s can be hard to work with—it's not always clear how to +find the ((control point))s that provide the ((shape)) you are looking +for. Sometimes you can compute them, and sometimes you'll just have to +find a suitable value by trial and error. + +{{index "arc method", arc}} + +The `arc` method is a way to draw a line that curves along the edge of +a circle. It takes a pair of ((coordinates)) for the arc's center, a +radius, and then a start angle and end angle. + +{{index pi, "Math.PI constant"}} + +Those last two parameters make it possible to draw only part of the +circle. The ((angle))s are measured in ((radian))s, not ((degree))s. +This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, +which is about 6.28. The angle starts counting at the point to the +right of the circle's center and goes clockwise from there. You can +use a start of 0 and an end bigger than 2π (say, 7) to draw a full +circle. + +```{lang: "text/html"} + + +``` + +{{index "moveTo method", "arc method", [path, " canvas"]}} + +The resulting picture contains a ((line)) from the right of the full +circle (first call to `arc`) to the right of the quarter-((circle)) +(second call). Like other path-drawing methods, a line drawn with +`arc` is connected to the previous path segment. You can call `moveTo` +or start a new path to avoid this. + +{{if book + +{{figure {url: "img/canvas_circle.png", alt: "Drawing a circle",width: "4.9cm"}}} + +if}} + +{{id pie_chart}} + +## Drawing a pie chart + +{{index "pie chart example"}} + +Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your +first assignment is to draw a pie chart of its customer satisfaction +((survey)) results. + +The `results` binding contains an array of objects that represent the +survey responses. + +```{sandbox: "pie", includeCode: true} +const results = [ + {name: "Satisfied", count: 1043, color: "lightblue"}, + {name: "Neutral", count: 563, color: "lightgreen"}, + {name: "Unsatisfied", count: 510, color: "pink"}, + {name: "No comment", count: 175, color: "silver"} +]; +``` + +{{index "pie chart example"}} + +To draw a pie chart, we draw a number of pie slices, each made up of +an ((arc)) and a pair of ((line))s to the center of that arc. We can +compute the ((angle)) taken up by each arc by dividing a full circle +(2π) by the total number of responses and then multiplying that number +(the angle per response) by the number of people who picked a given +choice. + +```{lang: "text/html", sandbox: "pie"} + + +``` + +{{if book + +This draws the following chart: + +{{figure {url: "img/canvas_pie_chart.png", alt: "A pie chart",width: "5cm"}}} + +if}} + +But a chart that doesn't tell us what the slices mean isn't very +helpful. We need a way to draw text to the ((canvas)). + +## Text + +{{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} + +A 2D canvas drawing context provides the methods `fillText` and +`strokeText`. The latter can be useful for outlining letters, but +usually `fillText` is what you need. It will fill the outline of the +given ((text)) with the current `fillStyle`. + +```{lang: "text/html"} + + +``` + +You can specify the size, style, and ((font)) of the text with the +`font` property. This example just gives a font size and family name. +It is also possible to add `italic` or `bold` to the start of the +string to select a style. + +{{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} + +The last two arguments to `fillText` and `strokeText` provide the +position at which the font is drawn. By default, they indicate the +position of the start of the text's alphabetic baseline, which is the +line that letters "stand" on, not counting hanging parts in letters +such as _j_ or _p_. You can change the horizontal position by setting the +`textAlign` property to `"end"` or `"center"` and the vertical +position by setting `textBaseline` to `"top"`, `"middle"`, or +`"bottom"`. + +{{index "pie chart example"}} + +We'll come back to our pie chart, and the problem of ((label))ing the +slices, in the [exercises](canvas#exercise_pie_chart) at the end of +the chapter. + +## Images + +{{index "vector graphics", "bitmap graphics"}} + +In computer ((graphics)), a distinction is often made between _vector_ +graphics and _bitmap_ graphics. The first is what we have been doing +so far in this chapter—specifying a picture by giving a logical +description of ((shape))s. Bitmap graphics, on the other hand, don't +specify actual shapes but rather work with ((pixel)) data (rasters of +colored dots). + +{{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} + +The `drawImage` method allows us to draw ((pixel)) data onto a +((canvas)). This pixel data can originate from an `` element or +from another canvas. The following example creates a detached `` +element and loads an image file into it. But it cannot immediately +start drawing from this picture because the browser may not have +loaded it yet. To deal with this, we register a `"load"` event handler +and do the drawing after the image has loaded. + +```{lang: "text/html"} + + +``` + +{{index "drawImage method", scaling}} + +By default, `drawImage` will draw the image at its original size. You +can also give it two additional arguments to set a different width +and height. + +When `drawImage` is given _nine_ arguments, it can be used to draw +only a fragment of an image. The second through fifth arguments +indicate the rectangle (x, y, width, and height) in the source image +that should be copied, and the sixth to ninth arguments give the +rectangle (on the canvas) into which it should be copied. + +{{index "player", "pixel art"}} + +This can be used to pack multiple _((sprite))s_ (image elements) into +a single image file and then draw only the part you need. For example, +we have this picture containing a game character in multiple +((pose))s: + +{{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} + +{{index [animation, "platform game"]}} + +By alternating which pose we draw, we can show an animation that +looks like a walking character. + +{{index "fillRect method", "clearRect method", clearing}} + +To animate a ((picture)) on a ((canvas)), the `clearRect` method is +useful. It resembles `fillRect`, but instead of coloring the +rectangle, it makes it ((transparent)), removing the previously drawn +pixels. + +{{index "setInterval function", "img (HTML tag)"}} + +We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide +and 30 pixels high. The following code loads the image and then sets +up an interval (repeated timer) to draw the next ((frame)): + +```{lang: "text/html"} + + +``` + +{{index "remainder operator", "% operator", [animation, "platform game"]}} + +The `cycle` binding tracks our position in the animation. For each +((frame)), it is incremented and then clipped back to the 0 to 7 range +by using the remainder operator. This binding is then used to compute +the x-coordinate that the sprite for the current pose has in the +picture. + +## Transformation + +{{index transformation, mirroring}} + +{{indexsee flipping, mirroring}} + +But what if we want our character to walk to the left instead of to +the right? We could draw another set of sprites, of course. But we can +also instruct the ((canvas)) to draw the picture the other way round. + +{{index "scale method", scaling}} + +Calling the `scale` method will cause anything drawn after it to be +scaled. This method takes two parameters, one to set a horizontal +scale and one to set a vertical scale. + +```{lang: "text/html"} + + +``` + +{{if book + +Because of the call to `scale`, the circle is drawn three times as wide +and half as high. + +{{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} + +if}} + +{{index mirroring}} + +Scaling will cause everything about the drawn image, including the +((line width)), to be stretched out or squeezed together as specified. +Scaling by a negative amount will flip the picture around. The +flipping happens around point (0,0), which means it will also flip the +direction of the coordinate system. When a horizontal scaling of -1 is +applied, a shape drawn at x position 100 will end up at what used to +be position -100. + +{{index "drawImage method"}} + +So to turn a picture around, we can't simply add `cx.scale(-1, 1)` +before the call to `drawImage` because that would move our picture +outside of the ((canvas)), where it won't be visible. You could adjust +the ((coordinates)) given to `drawImage` to compensate for this by +drawing the image at x position -50 instead of 0. Another solution, +which doesn't require the code that does the drawing to know about the +scale change, is to adjust the ((axis)) around which the scaling +happens. + +{{index "rotate method", "translate method", transformation}} + +There are several other methods besides `scale` that influence the +coordinate system for a ((canvas)). You can rotate subsequently drawn +shapes with the `rotate` method and move them with the `translate` +method. The interesting—and confusing—thing is that these +transformations _stack_, meaning that each one happens relative to the +previous transformations. + +{{index "rotate method", "translate method"}} + +So if we translate by 10 horizontal pixels twice, everything will be +drawn 20 pixels to the right. If we first move the center of the +coordinate system to (50,50) and then rotate by 20 ((degree))s (about +0.1π ((radian))s), that rotation will happen _around_ point (50,50). + +{{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} + +{{index coordinates}} + +But if we _first_ rotate by 20 degrees and _then_ translate by +(50,50), the translation will happen in the rotated coordinate system +and thus produce a different orientation. The order in which +transformations are applied matters. + +{{index axis, mirroring}} + +To flip a picture around the vertical line at a given x position, we +can do the following: + +```{includeCode: true} +function flipHorizontally(context, around) { + context.translate(around, 0); + context.scale(-1, 1); + context.translate(-around, 0); +} +``` + +{{index "flipHorizontally method"}} + +We move the y-((axis)) to where we want our ((mirror)) to be, apply +the mirroring, and finally move the y-axis back to its proper place in +the mirrored universe. The following picture explains why this works: + +{{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} + +{{index "translate method", "scale method", transformation, canvas}} + +This shows the coordinate systems before and after mirroring across +the central line. The triangles are numbered to illustrate each step. +If we draw a triangle at a positive x position, it would, by default, +be in the place where triangle 1 is. A call to `flipHorizontally` +first does a translation to the right, which gets us to triangle 2. It +then scales, flipping the triangle over to position 3. This is not +where it should be, if it were mirrored in the given line. The second +`translate` call fixes this—it "cancels" the initial translation and +makes triangle 4 appear exactly where it should. + +We can now draw a mirrored character at position (100,0) by flipping +the world around the character's vertical center. + +```{lang: "text/html"} + + +``` + +## Storing and clearing transformations + +{{index "side effect", canvas, transformation}} + +Transformations stick around. Everything else we draw after +((drawing)) that mirrored character would also be mirrored. That might +be inconvenient. + +It is possible to save the current transformation, do some drawing and +transforming, and then restore the old transformation. This is usually +the proper thing to do for a function that needs to temporarily +transform the coordinate system. First, we save whatever +transformation the code that called the function was using. Then the +function does its thing, adding more transformations on top of the +current transformation. Finally, we revert to the +transformation we started with. + +{{index "save method", "restore method", [state, "of canvas"]}} + +The `save` and `restore` methods on the 2D ((canvas)) context do this +((transformation)) management. They conceptually keep a stack of +transformation states. When you call `save`, the current state is +pushed onto the stack, and when you call `restore`, the state on top +of the stack is taken off and used as the context's current +transformation. You can also call `resetTransform` to fully reset the +transformation. + +{{index "branching recursion", "fractal example", recursion}} + +The `branch` function in the following example illustrates what you +can do with a function that changes the transformation and then calls +a function (in this case itself), which continues drawing with +the given transformation. + +This function draws a treelike shape by drawing a line, moving the +center of the coordinate system to the end of the line, and calling +itself twice—first rotated to the left and then rotated to the right. +Every call reduces the length of the branch drawn, and the recursion +stops when the length drops below 8. + +```{lang: "text/html"} + + +``` + +{{if book + +The result is a simple fractal. + +{{figure {url: "img/canvas_tree.png", alt: "A recursive picture",width: "5cm"}}} + +if}} + +{{index "save method", "restore method", canvas, "rotate method"}} + +If the calls to `save` and `restore` were not there, the second +recursive call to `branch` would end up with the position and rotation +created by the first call. It wouldn't be connected to the current +branch but rather to the innermost, rightmost branch drawn by the +first call. The resulting shape might also be interesting, but it is +definitely not a tree. + +{{id canvasdisplay}} + +## Back to the game + +{{index "drawImage method"}} + +We now know enough about ((canvas)) drawing to start working on a +((canvas))-based ((display)) system for the ((game)) from the +[previous chapter](game). The new display will no longer be showing +just colored boxes. Instead, we'll use `drawImage` to draw pictures +that represent the game's elements. + +{{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} + +We define another display object type called `CanvasDisplay`, +supporting the same interface as `DOMDisplay` from [Chapter +?](game#domdisplay), namely, the methods `syncState` and `clear`. + +{{index [state, "in objects"]}} + +This object keeps a little more information than `DOMDisplay`. Rather +than using the scroll position of its DOM element, it tracks its own +((viewport)), which tells us what part of the level we are currently +looking at. Finally, it keeps a `flipPlayer` property so that even +when the player is standing still, it keeps facing the direction it +last moved in. + +```{sandbox: "game", includeCode: true} +class CanvasDisplay { + constructor(parent, level) { + this.canvas = document.createElement("canvas"); + this.canvas.width = Math.min(600, level.width * scale); + this.canvas.height = Math.min(450, level.height * scale); + parent.appendChild(this.canvas); + this.cx = this.canvas.getContext("2d"); + + this.flipPlayer = false; + + this.viewport = { + left: 0, + top: 0, + width: this.canvas.width / scale, + height: this.canvas.height / scale + }; + } + + clear() { + this.canvas.remove(); + } +} +``` + +The `syncState` method first computes a new viewport and then draws +the game scene at the appropriate position. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.syncState = function(state) { + this.updateViewport(state); + this.clearDisplay(state.status); + this.drawBackground(state.level); + this.drawActors(state.actors); +}; +``` + +{{index scrolling, clearing}} + +Contrary to `DOMDisplay`, this display style _does_ have to redraw the +background on every update. Because shapes on a canvas are just +((pixel))s, after we draw them there is no good way to move them (or +remove them). The only way to update the canvas display is to clear it +and redraw the scene. We may also have scrolled, which requires the +background to be in a different position. + +{{index "CanvasDisplay class"}} + +The `updateViewport` method is similar to `DOMDisplay`'s +`scrollPlayerIntoView` method. It checks whether the player is too +close to the edge of the screen and moves the ((viewport)) when this +is the case. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.updateViewport = function(state) { + let view = this.viewport, margin = view.width / 3; + let player = state.player; + let center = player.pos.plus(player.size.times(0.5)); + + if (center.x < view.left + margin) { + view.left = Math.max(center.x - margin, 0); + } else if (center.x > view.left + view.width - margin) { + view.left = Math.min(center.x + margin - view.width, + state.level.width - view.width); + } + if (center.y < view.top + margin) { + view.top = Math.max(center.y - margin, 0); + } else if (center.y > view.top + view.height - margin) { + view.top = Math.min(center.y + margin - view.height, + state.level.height - view.height); + } +}; +``` + +{{index boundary, "Math.max function", "Math.min function", clipping}} + +The calls to `Math.max` and `Math.min` ensure that the viewport does +not end up showing space outside of the level. `Math.max(x, 0)` makes +sure the resulting number is not less than zero. `Math.min` +similarly guarantees that a value stays below a given bound. + +When ((clearing)) the display, we'll use a slightly different +((color)) depending on whether the game is won (brighter) or lost +(darker). + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.clearDisplay = function(status) { + if (status == "won") { + this.cx.fillStyle = "rgb(68, 191, 255)"; + } else if (status == "lost") { + this.cx.fillStyle = "rgb(44, 136, 214)"; + } else { + this.cx.fillStyle = "rgb(52, 166, 251)"; + } + this.cx.fillRect(0, 0, + this.canvas.width, this.canvas.height); +}; +``` + +{{index "Math.floor function", "Math.ceil function", rounding}} + +To draw the background, we run through the tiles that are visible in +the current viewport, using the same trick used in the `touches` +method from the [previous chapter](game#touches). + +```{sandbox: "game", includeCode: true} +let otherSprites = document.createElement("img"); +otherSprites.src = "img/sprites.png"; + +CanvasDisplay.prototype.drawBackground = function(level) { + let {left, top, width, height} = this.viewport; + let xStart = Math.floor(left); + let xEnd = Math.ceil(left + width); + let yStart = Math.floor(top); + let yEnd = Math.ceil(top + height); + + for (let y = yStart; y < yEnd; y++) { + for (let x = xStart; x < xEnd; x++) { + let tile = level.rows[y][x]; + if (tile == "empty") continue; + let screenX = (x - left) * scale; + let screenY = (y - top) * scale; + let tileX = tile == "lava" ? scale : 0; + this.cx.drawImage(otherSprites, + tileX, 0, scale, scale, + screenX, screenY, scale, scale); + } + } +}; +``` + +{{index "drawImage method", sprite, tile}} + +Tiles that are not empty are drawn with `drawImage`. The +`otherSprites` image contains the pictures used for elements other +than the player. It contains, from left to right, the wall tile, the +lava tile, and the sprite for a coin. + +{{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} + +{{index scaling}} + +Background tiles are 20 by 20 pixels since we will use the same scale +that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 +(the value of the `scale` binding), and the offset for walls is 0. + +{{index drawing, "load event", "drawImage method"}} + +We don't bother waiting for the sprite image to load. Calling +`drawImage` with an image that hasn't been loaded yet will simply do +nothing. Thus, we might fail to draw the game properly for the first +few ((frame))s, while the image is still loading, but that is not a +serious problem. Since we keep updating the screen, the correct scene +will appear as soon as the loading finishes. + +{{index "player", [animation, "platform game"], drawing}} + +The ((walking)) character shown earlier will be used to represent the +player. The code that draws it needs to pick the right ((sprite)) and +direction based on the player's current motion. The first eight +sprites contain a walking animation. When the player is moving along a +floor, we cycle through them based on the current time. We want to +switch frames every 60 milliseconds, so the ((time)) is divided by 60 +first. When the player is standing still, we draw the ninth sprite. +During jumps, which are recognized by the fact that the vertical speed +is not zero, we use the tenth, rightmost sprite. + +{{index "flipHorizontally function", "CanvasDisplay class"}} + +Because the ((sprite))s are slightly wider than the player object—24 +instead of 16 pixels to allow some space for feet and arms—the method +has to adjust the x-coordinate and width by a given amount +(`playerXOverlap`). + +```{sandbox: "game", includeCode: true} +let playerSprites = document.createElement("img"); +playerSprites.src = "img/player.png"; +const playerXOverlap = 4; + +CanvasDisplay.prototype.drawPlayer = function(player, x, y, + width, height){ + width += playerXOverlap * 2; + x -= playerXOverlap; + if (player.speed.x != 0) { + this.flipPlayer = player.speed.x < 0; + } + + let tile = 8; + if (player.speed.y != 0) { + tile = 9; + } else if (player.speed.x != 0) { + tile = Math.floor(Date.now() / 60) % 8; + } + + this.cx.save(); + if (this.flipPlayer) { + flipHorizontally(this.cx, x + width / 2); + } + let tileX = tile * width; + this.cx.drawImage(playerSprites, tileX, 0, width, height, + x, y, width, height); + this.cx.restore(); +}; +``` + +The `drawPlayer` method is called by `drawActors`, which is +responsible for drawing all the actors in the game. + +```{sandbox: "game", includeCode: true} +CanvasDisplay.prototype.drawActors = function(actors) { + for (let actor of actors) { + let width = actor.size.x * scale; + let height = actor.size.y * scale; + let x = (actor.pos.x - this.viewport.left) * scale; + let y = (actor.pos.y - this.viewport.top) * scale; + if (actor.type == "player") { + this.drawPlayer(actor, x, y, width, height); + } else { + let tileX = (actor.type == "coin" ? 2 : 1) * scale; + this.cx.drawImage(otherSprites, + tileX, 0, width, height, + x, y, width, height); + } + } +}; +``` + +When ((drawing)) something that is not the ((player)), we look at its +type to find the offset of the correct sprite. The ((lava)) tile is +found at offset 20, and the ((coin)) sprite is found at 40 (two times +`scale`). + +{{index viewport}} + +We have to subtract the viewport's position when computing the actor's +position since (0,0) on our ((canvas)) corresponds to the top left of +the viewport, not the top left of the level. We could also have used +`translate` for this. Either way works. + +{{if interactive + +This document plugs the new display into `runGame`: + +```{lang: "text/html", sandbox: game, focus: yes, startCode: true} + + + +``` + +if}} + +{{if book + +{{index [game, screenshot], [game, "with canvas"]}} + +That concludes the new ((display)) system. The resulting game looks +something like this: + +{{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} + +if}} + +{{id graphics_tradeoffs}} + +## Choosing a graphics interface + +So when you need to generate graphics in the browser, you can choose +between plain HTML, ((SVG)), and ((canvas)). There is no single +_best_ approach that works in all situations. Each option has +strengths and weaknesses. + +{{index "text wrapping"}} + +Plain HTML has the advantage of being simple. It also integrates well +with ((text)). Both SVG and canvas allow you to draw text, but they +won't help you position that text or wrap it when it takes up more +than one line. In an HTML-based picture, it is much easier to include +blocks of text. + +{{index zooming, SVG}} + +SVG can be used to produce ((crisp)) ((graphics)) that look good at +any zoom level. Unlike HTML, it is designed for drawing +and is thus more suitable for that purpose. + +{{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} + +Both SVG and HTML build up a data structure (the DOM) that +represents your picture. This makes it possible to modify elements +after they are drawn. If you need to repeatedly change a small part of +a big ((picture)) in response to what the user is doing or as part of +an ((animation)), doing it in a canvas can be needlessly expensive. +The DOM also allows us to register mouse event handlers on every +element in the picture (even on shapes drawn with SVG). You can't do +that with canvas. + +{{index performance, optimization}} + +But ((canvas))'s ((pixel))-oriented approach can be an advantage when +drawing a huge number of tiny elements. The fact that it does not +build up a data structure but only repeatedly draws onto the same +pixel surface gives canvas a lower cost per shape. + +{{index "ray tracer"}} + +There are also effects, such as rendering a scene one pixel at a time +(for example, using a ray tracer) or postprocessing an image with +JavaScript (blurring or distorting it), that can be realistically +handled only by a ((pixel))-based approach. + +In some cases, you may want to combine several of these techniques. +For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but +show ((text))ual information by positioning an HTML element on top +of the picture. + +{{index display}} + +For nondemanding applications, it really doesn't matter much which +interface you choose. The display we built for our game in this +chapter could have been implemented using any of these three +((graphics)) technologies since it does not need to draw text, handle +mouse interaction, or work with an extraordinarily large number of +elements. + +## Summary + +In this chapter we discussed techniques for drawing graphics in the +browser, focusing on the `` element. + +A canvas node represents an area in a document that our program may +draw on. This drawing is done through a drawing context object, +created with the `getContext` method. + +The 2D drawing interface allows us to fill and stroke various shapes. +The context's `fillStyle` property determines how shapes are filled. +The `strokeStyle` and `lineWidth` properties control the way lines are +drawn. + +Rectangles and pieces of text can be drawn with a single method call. +The `fillRect` and `strokeRect` methods draw rectangles, and the +`fillText` and `strokeText` methods draw text. To create custom +shapes, we must first build up a path. + +{{index stroking, filling}} + +Calling `beginPath` starts a new path. A number of other methods add +lines and curves to the current path. For example, `lineTo` can add a +straight line. When a path is finished, it can be filled with the +`fill` method or stroked with the `stroke` method. + +Moving pixels from an image or another canvas onto our canvas is done +with the `drawImage` method. By default, this method draws the whole +source image, but by giving it more parameters, you can copy a +specific area of the image. We used this for our game by copying +individual poses of the game character out of an image that contained +many such poses. + +Transformations allow you to draw a shape in multiple orientations. A +2D drawing context has a current transformation that can be changed +with the `translate`, `scale`, and `rotate` methods. These will affect +all subsequent drawing operations. A transformation state can be saved +with the `save` method and restored with the `restore` method. + +When showing an animation on a canvas, the `clearRect` method can be +used to clear part of the canvas before redrawing it. + +## Exercises + +### Shapes + +{{index "shapes (exercise)"}} + +Write a program that draws the following ((shape))s on a ((canvas)): + +{{index rotation}} + +1. A ((trapezoid)) (a ((rectangle)) that is wider on one side) + +2. A red ((diamond)) (a rectangle rotated 45 degrees or ¼π radians) + +3. A zigzagging ((line)) + +4. A ((spiral)) made up of 100 straight line segments + +5. A yellow ((star)) + +{{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} + +When drawing the last two, you may want to refer to the explanation of +`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes +how to get coordinates on a circle using these functions. + +{{index readability, "hard-coding"}} + +I recommend creating a function for each shape. Pass the position, and +optionally other properties such as the size or the number of points, +as parameters. The alternative, which is to hard-code numbers all over +your code, tends to make the code needlessly hard to read and modify. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index [path, canvas], "shapes (exercise)"}} + +The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable +center coordinates and add each of the four corners around the center. + +{{index "flipHorizontally function", rotation}} + +The ((diamond)) (2) can be drawn the straightforward way, with a path, +or the interesting way, with a `rotate` ((transformation)). To use +rotation, you will have to apply a trick similar to what we did in the +`flipHorizontally` function. Because you want to rotate around the +center of your rectangle and not around the point (0,0), you must +first `translate` to there, then rotate, and then translate back. + +Make sure you reset the transformation after drawing any shape that +creates one. + +{{index "remainder operator", "% operator"}} + +For the ((zigzag)) (3) it becomes impractical to write a new call to +`lineTo` for each line segment. Instead, you should use a ((loop)). +You can have each iteration draw either two ((line)) segments (right +and then left again) or one, in which case you must use the evenness +(`% 2`) of the loop index to determine whether to go left or right. + +You'll also need a loop for the ((spiral)) (4). If you draw a series +of points, with each point moving further along a circle around the +spiral's center, you get a circle. If, during the loop, you vary the +radius of the circle on which you are putting the current point and go +around more than once, the result is a spiral. + +{{index "quadraticCurveTo method"}} + +The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. +You could also draw one with straight lines. Divide a circle into +eight pieces for a star with eight points, or however many pieces you +want. Draw lines between these points, making them curve toward the +center of the star. With `quadraticCurveTo`, you can use the center as +the control point. + +hint}} + +{{id exercise_pie_chart}} + +### The pie chart + +{{index label, text, "pie chart example"}} + +[Earlier](canvas#pie_chart) in the chapter, we saw an example program +that drew a pie chart. Modify this program so that the name of each +category is shown next to the slice that represents it. Try to find a +pleasing-looking way to automatically position this text that would +work for other data sets as well. You may assume that categories are +big enough to leave ample room for their labels. + +You might need `Math.sin` and `Math.cos` again, which are described in +[Chapter ?](dom#sin_cos). + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} + +You will need to call `fillText` and set the context's `textAlign` and +`textBaseline` properties in such a way that the text ends up where +you want it. + +A sensible way to position the labels would be to put the text on the +line going from the center of the pie through the middle of the slice. +You don't want to put the text directly against the side of the pie +but rather move the text out to the side of the pie by a given number +of pixels. + +The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The +following code finds a position on this line 120 pixels from the +center: + +```{test: no} +let middleAngle = currentAngle + 0.5 * sliceAngle; +let textX = Math.cos(middleAngle) * 120 + centerX; +let textY = Math.sin(middleAngle) * 120 + centerY; +``` + +For `textBaseline`, the value `"middle"` is probably appropriate when +using this approach. What to use for `textAlign` depends on which side +of the circle we are on. On the left, it should be `"right"`, and on +the right, it should be `"left"`, so that the text is positioned away +from the pie. + +{{index "Math.cos function"}} + +If you are not sure how to find out which side of the circle a given +angle is on, look to the explanation of `Math.cos` in [Chapter +?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it +corresponds to, which in turn tells us exactly which side of the +circle we are on. + +hint}} + +### A bouncing ball + +{{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} + +Use the `requestAnimationFrame` technique that we saw in [Chapter +?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a +((box)) with a bouncing ((ball)) in it. The ball moves at a constant +((speed)) and bounces off the box's sides when it hits them. + +{{if interactive + +```{lang: "text/html", test: no} + + +``` + +if}} + +{{hint + +{{index "strokeRect method", animation, "arc method"}} + +A ((box)) is easy to draw with `strokeRect`. Define a binding that +holds its size or define two bindings if your box's width and height +differ. To create a round ((ball)), start a path and call `arc(x, y, +radius, 0, 7)`, which creates an arc going from zero to more than a +whole circle. Then fill the path. + +{{index "collision detection", "Vec class"}} + +To model the ball's position and ((speed)), you can use the `Vec` +class from [Chapter ?](game#vector)[ (which is available on this +page)]{if interactive}. Give it a starting speed, preferably one that +is not purely vertical or horizontal, and for every ((frame)) multiply +that speed by the amount of time that elapsed. When the ball gets +too close to a vertical wall, invert the x component in its speed. +Likewise, invert the y component when it hits a horizontal wall. + +{{index "clearRect method", clearing}} + +After finding the ball's new position and speed, use `clearRect` to +delete the scene and redraw it using the new position. + +hint}} + +### Precomputed mirroring + +{{index optimization, "bitmap graphics", mirror}} + +One unfortunate thing about ((transformation))s is that they slow down +the drawing of bitmaps. The position and size of each ((pixel)) has to be +transformed, and though it is possible that ((browser))s will get +cleverer about transformation in the ((future)), they currently cause a +measurable increase in the time it takes to draw a bitmap. + +In a game like ours, where we are drawing only a single transformed +sprite, this is a nonissue. But imagine that we need to draw hundreds +of characters or thousands of rotating particles from an explosion. + +Think of a way to allow us to draw an inverted character without +loading additional image files and without having to make transformed +`drawImage` calls every frame. + +{{hint + +{{index mirror, scaling, "drawImage method"}} + +The key to the solution is the fact that we can use a ((canvas)) +element as a source image when using `drawImage`. It is possible to +create an extra `` element, without adding it to the document, +and draw our inverted sprites to it, once. When drawing an actual +frame, we just copy the already inverted sprites to the main canvas. + +{{index "load event"}} + +Some care would be required because images do not load instantly. We +do the inverted drawing only once, and if we do it before the image +loads, it won't draw anything. A `"load"` handler on the image can be +used to draw the inverted images to the extra canvas. This canvas can +be used as a drawing source immediately (it'll simply be blank until +we draw the character onto it). + +hint}} + From c1989c6c3fc63b6e7cdc804490ae03135d4ddae2 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 23 Oct 2020 17:36:02 -0500 Subject: [PATCH 06/22] =?UTF-8?q?Segunda=20secci=C3=B3n:=20SVG=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 024ef9ba5..4e17c6524 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -50,16 +50,15 @@ con la figura en una nueva posición. ## SVG -This book will not go into ((SVG)) in detail, but I will briefly -explain how it works. At the [end of the -chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs -that you must consider when deciding which ((drawing)) mechanism is -appropriate for a given application. +Este libro no hablará de ((SVG)) a detalle, pero explicaré +de forma breve como funciona. Al final de [final del capítulo](canvas#graphics_tradeoffs), regresaré a estos temas +que debes considerar cuando debas decidir cuál mecanismo de ((dibujo)) sea +apropiado dada una aplicación. -This is an HTML document with a simple SVG ((picture)) in it: +Este es un documento HTML con una ((imagen)) SVG simple: ```{lang: "text/html", sandbox: "svg"} -

Normal HTML here.

+

HTML normal aquí.

` -and `` tags, which do not exist in HTML, do have a meaning in -SVG—they draw shapes using the style and position specified by their -attributes. +El atributo `xmlns` cambia un elemento (y sus hijos) a un +_XML namespace_ diferente. Este _namespace_, identificado por una ((URL)), +especifica el dialecto que estamos usando. las etiquetas +`` y ``, —que no existen en HTML, pero tienen un significado en +SVG— dibujan formas usando el estilo y posición especificados por sus +atributos. {{if book -The document is displayed like this: +El documento muestra algo así: -{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} +{{figure {url: "img/svg-demo.png", alt: "Una imagen SVG embebida",width: "4.5cm"}}} if}} {{index [DOM, graphics]}} -These tags create DOM elements, just like HTML tags, that -scripts can interact with. For example, this changes the `` -element to be ((color))ed cyan instead: +Estas etiquetas crean elementos en el DOM, como etiquetas de HTML, con las +que los scripts pueden interactuar. Por ejemplo, el siguiente código cambia el elemento `` +para que sea ((color))eado de cyan: ```{sandbox: "svg"} -let circle = document.querySelector("circle"); -circle.setAttribute("fill", "cyan"); +let circulo = document.querySelector("circle"); +circulo.setAttribute("fill", "cyan"); ``` -## The canvas element +## El elemento canvas {{index [canvas, size], "canvas (HTML tag)"}} From 5f1dfd135e98f0cd64fb666b4281283629ecc120 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 24 Oct 2020 16:31:23 -0500 Subject: [PATCH 07/22] Parrafos traducidos --- 17_canvas.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/17_canvas.md b/17_canvas.md index 4e17c6524..002b56023 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -50,6 +50,7 @@ con la figura en una nueva posición. ## SVG +<<<<<<< HEAD Este libro no hablará de ((SVG)) a detalle, pero explicaré de forma breve como funciona. Al final de [final del capítulo](canvas#graphics_tradeoffs), regresaré a estos temas que debes considerar cuando debas decidir cuál mecanismo de ((dibujo)) sea @@ -59,6 +60,18 @@ Este es un documento HTML con una ((imagen)) SVG simple: ```{lang: "text/html", sandbox: "svg"}

HTML normal aquí.

+======= +This book will not go into ((SVG)) in detail, but I will briefly +explain how it works. At the [end of the +chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs +that you must consider when deciding which ((drawing)) mechanism is +appropriate for a given application. + +This is an HTML document with a simple SVG ((picture)) in it: + +```{lang: "text/html", sandbox: "svg"} +

Normal HTML here.

+>>>>>>> 12e744e... Primera sección traducida ` +and `` tags, which do not exist in HTML, do have a meaning in +SVG—they draw shapes using the style and position specified by their +attributes. + +{{if book + +The document is displayed like this: + +{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} +>>>>>>> 12e744e... Primera sección traducida if}} {{index [DOM, graphics]}} +<<<<<<< HEAD Estas etiquetas crean elementos en el DOM, como etiquetas de HTML, con las que los scripts pueden interactuar. Por ejemplo, el siguiente código cambia el elemento `` para que sea ((color))eado de cyan: @@ -95,6 +124,18 @@ circulo.setAttribute("fill", "cyan"); ``` ## El elemento canvas +======= +These tags create DOM elements, just like HTML tags, that +scripts can interact with. For example, this changes the `` +element to be ((color))ed cyan instead: + +```{sandbox: "svg"} +let circle = document.querySelector("circle"); +circle.setAttribute("fill", "cyan"); +``` + +## The canvas element +>>>>>>> 12e744e... Primera sección traducida {{index [canvas, size], "canvas (HTML tag)"}} From 1ce1a413ab36a76c1984df559569894ab7834a46 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 24 Oct 2020 16:38:04 -0500 Subject: [PATCH 08/22] Conflictos reparados --- 17_canvas.md | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 002b56023..4e17c6524 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -50,7 +50,6 @@ con la figura en una nueva posición. ## SVG -<<<<<<< HEAD Este libro no hablará de ((SVG)) a detalle, pero explicaré de forma breve como funciona. Al final de [final del capítulo](canvas#graphics_tradeoffs), regresaré a estos temas que debes considerar cuando debas decidir cuál mecanismo de ((dibujo)) sea @@ -60,18 +59,6 @@ Este es un documento HTML con una ((imagen)) SVG simple: ```{lang: "text/html", sandbox: "svg"}

HTML normal aquí.

-======= -This book will not go into ((SVG)) in detail, but I will briefly -explain how it works. At the [end of the -chapter](canvas#graphics_tradeoffs), I'll come back to the trade-offs -that you must consider when deciding which ((drawing)) mechanism is -appropriate for a given application. - -This is an HTML document with a simple SVG ((picture)) in it: - -```{lang: "text/html", sandbox: "svg"} -

Normal HTML here.

->>>>>>> 12e744e... Primera sección traducida ` -and `` tags, which do not exist in HTML, do have a meaning in -SVG—they draw shapes using the style and position specified by their -attributes. - -{{if book - -The document is displayed like this: - -{{figure {url: "img/svg-demo.png", alt: "An embedded SVG image",width: "4.5cm"}}} ->>>>>>> 12e744e... Primera sección traducida if}} {{index [DOM, graphics]}} -<<<<<<< HEAD Estas etiquetas crean elementos en el DOM, como etiquetas de HTML, con las que los scripts pueden interactuar. Por ejemplo, el siguiente código cambia el elemento `` para que sea ((color))eado de cyan: @@ -124,18 +95,6 @@ circulo.setAttribute("fill", "cyan"); ``` ## El elemento canvas -======= -These tags create DOM elements, just like HTML tags, that -scripts can interact with. For example, this changes the `` -element to be ((color))ed cyan instead: - -```{sandbox: "svg"} -let circle = document.querySelector("circle"); -circle.setAttribute("fill", "cyan"); -``` - -## The canvas element ->>>>>>> 12e744e... Primera sección traducida {{index [canvas, size], "canvas (HTML tag)"}} From 3c440d46893a01ad4138abeaa2733aecdf6bd61e Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 24 Oct 2020 17:08:43 -0500 Subject: [PATCH 09/22] =?UTF-8?q?Secci=C3=B3n=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index e8d94b4e8..502043aaf 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -163,35 +163,35 @@ la esquina superior izquierda. {{index filling, stroking, drawing, SVG}} En la interfaz del ((canvas)), una figura puede ser _rellenada_ dado -un cierto color o diseño, or it can be _stroked_, which -means a ((line)) is drawn along its edge. The same terminology is used -by SVG. +un cierto color o diseño, o puede ser _delimtada_, que +significa una ((linea)) dibujada en los bordes. La misma terminología aplica +para SVG. {{index "fillRect method", "strokeRect method"}} -The `fillRect` method fills a ((rectangle)). It takes first the x- and -y-((coordinates)) of the rectangle's top-left corner, then its width, -and then its height. A similar method, `strokeRect`, draws the -((outline)) of a rectangle. +El método `fillRect` rellena un ((rectángulo)). Usa primero las ((coordenadas)) `x` e +`y` desde la esquina superior izquierda del rectángulo, después su dimensiones de ancho +y alto. Un método parecido `strokeRect`, dibuja los +((bordes)) del rectángulo. {{index [state, "of canvas"]}} -Neither method takes any further parameters. The color of the fill, -thickness of the stroke, and so on, are not determined by an argument -to the method (as you might reasonably expect) but rather by -properties of the context object. +Ningún método toma parámetros adicionales. Tanto el color de relleno +como el grueso del orden, no son determinados por el argumento +del método (como podría esperarse normalmente) sino por las +propiedades del contexto del objeto. {{index filling, "fillStyle property"}} -The `fillStyle` property controls the way shapes are filled. It can be -set to a string that specifies a ((color)), using the color notation -used by ((CSS)). +La propiedad `fillStyle` controla La manera que se rellenan las figuras. Puede ser +estableciendo una cadena que especifique un ((color)), usando la misma notación +que en ((CSS)). {{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} -The `strokeStyle` property works similarly but determines the color -used for a stroked line. The width of that line is determined by the -`lineWidth` property, which may contain any positive number. +La propiedad `strokeStyle` funciona de forma similar, pero determinando el color +usado por la línea. El ancho de dicha línea es determinado por la +propiedad `lineWidth`, que puede ser cualquier número positivo. ```{lang: "text/html"} @@ -206,18 +206,17 @@ used for a stroked line. The width of that line is determined by the {{if book -This code draws two blue squares, using a thicker line for the second -one. +Este código dibuja dos cuadrados azules, usando un borde más delgado para el segundo. -{{figure {url: "img/canvas_stroke.png", alt: "Two stroked squares",width: "5cm"}}} +{{figure {url: "img/canvas_stroke.png", alt: "Dos cuadrados con borde",width: "5cm"}}} if}} {{index "default value", [canvas, size]}} -When no `width` or `height` attribute is specified, as in the example, -a canvas element gets a default width of 300 pixels and height of 150 -pixels. +Cuando no se especifican atributos `width` or `height` como en el ejemplo, +el canvas asigna un valor por defecto de 300 pixeles de ancho y 150 +pixeles de alto. ## Paths From e2efaa1bd0237eabb9ab32778cbc8821eca39b7e Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 24 Oct 2020 18:49:31 -0500 Subject: [PATCH 10/22] =?UTF-8?q?Secci=C3=B3n=20traducida?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 53 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 502043aaf..ba8b90aa5 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -218,15 +218,15 @@ Cuando no se especifican atributos `width` or `height` como en el ejemplo, el canvas asigna un valor por defecto de 300 pixeles de ancho y 150 pixeles de alto. -## Paths +## Rutas {{index [path, canvas], [interface, design], [canvas, path]}} -A path is a sequence of ((line))s. The 2D canvas interface takes a -peculiar approach to describing such a path. It is done entirely -through ((side effect))s. Paths are not values that can be stored and -passed around. Instead, if you want to do something with a path, you -make a sequence of method calls to describe its shape. +Una ruta es una secuencia de ((linea))s. La interfaz del canvas 2D +usa una forma particular para describir dicha ruta. Usualmente se infieren. +Las rutas no son valores que se puedan almacenar y usar. +En su lugar, si quieres hacer algo con una ruta, debes +hacer llamar a una secuencia de métodos para describir su figura. ```{lang: "text/html"} @@ -243,29 +243,28 @@ make a sequence of method calls to describe its shape. {{index canvas, "stroke method", "lineTo method", "moveTo method", shape}} -This example creates a path with a number of horizontal ((line)) -segments and then strokes it using the `stroke` method. Each segment -created with `lineTo` starts at the path's _current_ position. That -position is usually the end of the last segment, unless `moveTo` was -called. In that case, the next segment would start at the position +Este ejemplo crea una ruta con un número de segmentos de ((líneas)) +horizontales y las une usando el método `stroke`. Cada segmento +creado con `lineTo` empieza en la posición _actual_ de la ruta. Esa +posición suele ser la última del segmento anterior, a menos que `moveTo` fuera +llamada. En ese caso, the next segment would start at the position passed to `moveTo`. {{if book -The path described by the previous program looks like this: +La ruta descrita por el programa anterior se ve así: -{{figure {url: "img/canvas_path.png", alt: "Stroking a number of lines",width: "2.1cm"}}} +{{figure {url: "img/canvas_path.png", alt: "Uniendo un número de líneas",width: "2.1cm"}}} if}} {{index [path, canvas], filling, [path, closing], "fill method"}} -When filling a path (using the `fill` method), each ((shape)) is -filled separately. A path can contain multiple shapes—each `moveTo` -motion starts a new one. But the path needs to be _closed_ (meaning -its start and end are in the same position) before it can be filled. -If the path is not already closed, a line is added from its end to its -start, and the shape enclosed by the completed path is filled. +Cuando se llena una ruta (usando el método`fill`), cada ((figura)) es +rellenada de forma separada. Una ruta puede contener múltiples figuras —cada movimiento `moveTo` +comienza una nueva—. Pero la ruta debe _cerrarse_ (es decir, que empieza y termina en el mismo punto) antes de ser rellenada. +Si la ruta no se ha cerrado, se agrega una linea desde el final hasta el +principio, y la figura definida por la ruta será rellenada. ```{lang: "text/html"} @@ -279,22 +278,22 @@ start, and the shape enclosed by the completed path is filled. ``` -This example draws a filled triangle. Note that only two of the -triangle's sides are explicitly drawn. The third, from the -bottom-right corner back to the top, is implied and wouldn't be there -when you stroke the path. +Este ejemplo dibuja y rellena un triangulo. Nota que sólo dos de +sus lados estan explícitamente dibujados. El tercero, +de la esquina inferior derecha al vértice superior, está inferido y no debería estar ahí +cuando definas la ruta. {{if book -{{figure {url: "img/canvas_triangle.png", alt: "Filling a path",width: "2.2cm"}}} +{{figure {url: "img/canvas_triangle.png", alt: "Rellenando una ruta",width: "2.2cm"}}} if}} {{index "stroke method", "closePath method", [path, closing], canvas}} -You could also use the `closePath` method to explicitly close a path -by adding an actual ((line)) segment back to the path's start. This -segment _is_ drawn when stroking the path. +También podrías usar el método `closePath` para cerrar una ruta de +forma explícita agregando un segmento de ((linea)) de vuelta al inicio de la ruta. +Este segmento _es_ dibujado cuando delineas la ruta. ## Curves From 462e94f8044c72527aecfeb4b400cfc178b0be98 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 28 Oct 2020 15:33:35 -0600 Subject: [PATCH 11/22] =?UTF-8?q?Trauducida=20secci=C3=B3n.=20Texto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 189 +++++++++++++++++++++++++-------------------------- 1 file changed, 94 insertions(+), 95 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index ba8b90aa5..d33900a74 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -295,22 +295,21 @@ También podrías usar el método `closePath` para cerrar una ruta de forma explícita agregando un segmento de ((linea)) de vuelta al inicio de la ruta. Este segmento _es_ dibujado cuando delineas la ruta. -## Curves +## Curvas {{index [path, canvas], canvas, drawing}} -A path may also contain ((curve))d ((line))s. These are unfortunately -a bit more involved to draw. +Una ruta puede contener ((linea))s ((curva))s. Desafortunadamente +requieren algo más que dibujar. {{index "quadraticCurveTo method"}} -The `quadraticCurveTo` method draws a curve to a given point. To -determine the curvature of the line, the method is given a ((control -point)) as well as a destination point. Imagine this control point as -_attracting_ the line, giving it its curve. The line won't go through -the control point, but its direction at the start and end points will -be such that a straight line in that direction would point toward the -control point. The following example illustrates this: +El método `quadraticCurveTo` dibuja una curva dado un punto. +Para determinar la curvatura de la linea, el método proporciona un ((punto de +control)) que funciona como punto de destino. Imagina este punto de control como +_ancla_ de la línea, dándole su curvatura. La línea no irá a través +del punto de control, pero su dirección entre los puntos de inicio y cierre +será como una línea recta en esa dirección que guía el punto de control. Veamos el siguiente ejemplo: ```{lang: "text/html"} @@ -328,28 +327,28 @@ control point. The following example illustrates this: {{if book -It produces a path that looks like this: +Genera una ruta que se ve así: -{{figure {url: "img/canvas_quadraticcurve.png", alt: "A quadratic curve",width: "2.3cm"}}} +{{figure {url: "img/canvas_quadraticcurve.png", alt: "Una curva cuadrática",width: "2.3cm"}}} if}} {{index "stroke method"}} -We draw a ((quadratic curve)) from the left to the right, with (60,10) -as control point, and then draw two ((line)) segments going through -that control point and back to the start of the line. The result -somewhat resembles a _((Star Trek))_ insignia. You can see the effect -of the control point: the lines leaving the lower corners start off in -the direction of the control point and then ((curve)) toward their -target. +Dibujamos una ((curva cuadrática)) de la izquierda a la derecha, con la coordenada (60,10) +como punto de control, y dibujamos dos segmentos de ((linea)) a través +del punto de control y de regreso al principio de la línea. El resultado +se asemeja a una insignia de _((Star Trek))_. Puedes ver el efecto +del punto de control: las líneas empiezan en la esquina inferior +y toman la dirección del punto de control y se ((curvan)) hacia +el objetivo. {{index canvas, "bezierCurveTo method"}} -The `bezierCurveTo` method draws a similar kind of curve. Instead of a -single ((control point)), this one has two—one for each of the -((line))'s endpoints. Here is a similar sketch to illustrate the -behavior of such a curve: +El método `bezierCurveTo` dibuja un tipo de curva similar. En vez de +un sólo ((punto de control)), este posee dos para cada uno +de los vértices de la ((linea)). A continuación un ejemplo para +ilustrar el comportamiento de la curva: ```{lang: "text/html"} @@ -366,38 +365,39 @@ behavior of such a curve: ``` -The two control points specify the direction at both ends of the -curve. The farther they are away from their corresponding point, the -more the curve will "bulge" in that direction. +Ambos puntos de control especifican la dirección en la que ambos terminan +la curva. Mientras más lejos estén de los puntos de inicio correspondientes, más +se "abultará" la curva en esa dirección. {{if book -{{figure {url: "img/canvas_beziercurve.png", alt: "A bezier curve",width: "2.2cm"}}} +{{figure {url: "img/canvas_beziercurve.png", alt: "Una curva abultada",width: "2.2cm"}}} if}} {{index "trial and error"}} -Such ((curve))s can be hard to work with—it's not always clear how to -find the ((control point))s that provide the ((shape)) you are looking -for. Sometimes you can compute them, and sometimes you'll just have to -find a suitable value by trial and error. +Dichas ((curva))s pueden ser difíciled de trabajar, dado que no siempre +es posible encontrar los ((puntos de control)) que proporciona la ((figura)) que quieres +dibujar. A veces puedes calcularlos, y otras debes encontrar +el valor mediante prueba y error. {{index "arc method", arc}} -The `arc` method is a way to draw a line that curves along the edge of -a circle. It takes a pair of ((coordinates)) for the arc's center, a -radius, and then a start angle and end angle. +El método `arc` es una manera de dibujar una linea que se curva en el +borde de un circulo. Toma un par de ((coordenadas)) para el centro del arco, un +radio, y un ángulo de inicio y un ángulo de fin. {{index pi, "Math.PI constant"}} -Those last two parameters make it possible to draw only part of the -circle. The ((angle))s are measured in ((radian))s, not ((degree))s. -This means a full ((circle)) has an angle of 2π, or `2 * Math.PI`, -which is about 6.28. The angle starts counting at the point to the -right of the circle's center and goes clockwise from there. You can -use a start of 0 and an end bigger than 2π (say, 7) to draw a full -circle. +Estos últimos dos parámetros hacen posible dibujar sólo parte del +circulo. Los ((ángulo))s se miden en ((radian))es, no en ((grados)). +Esto implica que un ((círculo)) tiene un ángulo de 2π, or `2 * Math.PI`, +que es alrededor de 6.28. El ángulo comienza a contarse desde el +punto a la derecha del centro del círculo y sigue el sentido de +las manecillas del reloj. +Puedes usar un inicio de 0 y con un final mayor que 2π (digamos, 7) +para dibujar un círculo completo. ```{lang: "text/html"} @@ -414,67 +414,66 @@ circle. {{index "moveTo method", "arc method", [path, " canvas"]}} -The resulting picture contains a ((line)) from the right of the full -circle (first call to `arc`) to the right of the quarter-((circle)) -(second call). Like other path-drawing methods, a line drawn with -`arc` is connected to the previous path segment. You can call `moveTo` -or start a new path to avoid this. +La ilustración resultante muestra una ((línea)) desde la derecha del +círculo (llamando primero a `arc`) hasta la derecha de la semiluna +(segunda llamada). Al igual que otros métodos, una línea dibujada +con `arc` esta relacionada con la ruta del segmento anterior. +Puedes llamar a `moveTo` o empezar una nueva ruta para evitar esto. {{if book -{{figure {url: "img/canvas_circle.png", alt: "Drawing a circle",width: "4.9cm"}}} +{{figure {url: "img/canvas_circle.png", alt: "Dibujando un círcuo",width: "4.9cm"}}} if}} {{id pie_chart}} -## Drawing a pie chart +## Dibujando una gráfica de pastel {{index "pie chart example"}} -Imagine you've just taken a ((job)) at EconomiCorp, Inc., and your -first assignment is to draw a pie chart of its customer satisfaction -((survey)) results. +Imagina que tienes un ((trabajo)) en EconomiCorp, Inc., y tu +primera tarea es dibujar una gráfica de pastel de los resultados +de la ((encuesta)) de satifacción al cliente. -The `results` binding contains an array of objects that represent the -survey responses. +En este caso `resultados` contiene un arreglo de objetos que +representa las respuestas de las encuestas. ```{sandbox: "pie", includeCode: true} -const results = [ - {name: "Satisfied", count: 1043, color: "lightblue"}, +const resultados = [ + {name: "Satisfecho", count: 1043, color: "lightblue"}, {name: "Neutral", count: 563, color: "lightgreen"}, - {name: "Unsatisfied", count: 510, color: "pink"}, - {name: "No comment", count: 175, color: "silver"} + {name: "Insatisfecho", count: 510, color: "pink"}, + {name: "No comentó", count: 175, color: "silver"} ]; ``` {{index "pie chart example"}} -To draw a pie chart, we draw a number of pie slices, each made up of -an ((arc)) and a pair of ((line))s to the center of that arc. We can -compute the ((angle)) taken up by each arc by dividing a full circle -(2π) by the total number of responses and then multiplying that number -(the angle per response) by the number of people who picked a given -choice. +Para dibujar una gráfica de pastel, dibujamos un número de rebanadas, cada una hecha de +un ((arco)) y un par de ((línea))s hacia el centro de dicho arco. +Podemos calcular el ((ángulo)) de cada arco diviendo el círculo +(2π) entre el total de respuestas y multiplicar el resultado por el +(el ángulo por respuesta) por el numero de personas que seleccionaron una opción. ```{lang: "text/html", sandbox: "pie"} @@ -482,23 +481,22 @@ choice. {{if book -This draws the following chart: +Lo anterior muestra la siguiente gráfica: -{{figure {url: "img/canvas_pie_chart.png", alt: "A pie chart",width: "5cm"}}} +{{figure {url: "img/canvas_pie_chart.png", alt: "Una gráfica de pastel",width: "5cm"}}} if}} -But a chart that doesn't tell us what the slices mean isn't very -helpful. We need a way to draw text to the ((canvas)). +Pero una gráfica que no indica que significa las secciones no es +muy útil. Necesitamos mostrar texto en el ((canvas)). -## Text +## Texto {{index stroking, filling, "fillStyle property", "fillText method", "strokeText method"}} -A 2D canvas drawing context provides the methods `fillText` and -`strokeText`. The latter can be useful for outlining letters, but -usually `fillText` is what you need. It will fill the outline of the -given ((text)) with the current `fillStyle`. +El contexto de un dibujo en un canvas 2D proporciona los métodos +`fillText` y `strokeText`. El útltimo es muy útil para delinear las +letras, pero por lo general `fillText` es lo que usarás. Para el delinear el ((text)) con el `fillStyle` actual. ```{lang: "text/html"} @@ -506,33 +504,34 @@ given ((text)) with the current `fillStyle`. let cx = document.querySelector("canvas").getContext("2d"); cx.font = "28px Georgia"; cx.fillStyle = "fuchsia"; - cx.fillText("I can draw text, too!", 10, 50); + cx.fillText("¡También puedo dibujar texto!", 10, 50); ``` -You can specify the size, style, and ((font)) of the text with the -`font` property. This example just gives a font size and family name. -It is also possible to add `italic` or `bold` to the start of the -string to select a style. +Puedes específicar el tamaño, estilo, y ((fuente)) del texto con +la propiedad `font`. Este ejemplo sólo muestra un tamaño de fuente +y el nombre de la familia. +También es posible agregar `italic` (cursiva) o `bold` (negrita) +al principio de una cadena para darles un estilo. {{index "fillText method", "strokeText method", "textAlign property", "textBaseline property"}} -The last two arguments to `fillText` and `strokeText` provide the -position at which the font is drawn. By default, they indicate the -position of the start of the text's alphabetic baseline, which is the -line that letters "stand" on, not counting hanging parts in letters -such as _j_ or _p_. You can change the horizontal position by setting the -`textAlign` property to `"end"` or `"center"` and the vertical -position by setting `textBaseline` to `"top"`, `"middle"`, or -`"bottom"`. +Los últimos dos argumentos de `fillText` y `strokeText` indican la +posición en la cuál se dibujan las fuentes. Por defecto, indican +la posición del comienzo de la línea base del alfabeto del texto, +que es la línea en las que las letras se apoyan, no se cuentan las +letras con ganchos como la _j_ o la _p_. Puedes cambiar la +propiedad `textAlign` a `"end"` (final) o `"center"` (centrar) y +la posición vertical cambiando `textBaseline` a `"top"` (superior) +, `"middle"` (en medio), o`"bottom"` (inferior). {{index "pie chart example"}} -We'll come back to our pie chart, and the problem of ((label))ing the -slices, in the [exercises](canvas#exercise_pie_chart) at the end of -the chapter. +Regresaremos a nuestra gráfica de pastel, y el problema de ((etiquetar)) las +secciones, en los [ejercicios](canvas#exercise_pie_chart) al final +del capítulo. -## Images +## Imágenes {{index "vector graphics", "bitmap graphics"}} From 8f3fb77aad605e1ce9e1fae32dd8d6464a26ee43 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 29 Oct 2020 13:52:51 -0600 Subject: [PATCH 12/22] =?UTF-8?q?Secci=C3=B3n=20traducida.=20Im=C3=A1genes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 84 ++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index d33900a74..ef0dcef38 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -535,22 +535,22 @@ del capítulo. {{index "vector graphics", "bitmap graphics"}} -In computer ((graphics)), a distinction is often made between _vector_ -graphics and _bitmap_ graphics. The first is what we have been doing -so far in this chapter—specifying a picture by giving a logical -description of ((shape))s. Bitmap graphics, on the other hand, don't -specify actual shapes but rather work with ((pixel)) data (rasters of -colored dots). +En computación ((gráfica)), a menudo se distingue entre gráficos de _vectores_ +y gráficos de _mapa de bits_. Los primeros son los que hemos estado +trabajando en este capítulo, especificando una imagen mediante la +descripción lógica de sus ((figura))s. Los gráficos de mapa de bits, +por otro lado, especifican figuras, pero funcionan con datos de pixeles +(rejillas de puntos coloreados). {{index "load event", "event handling", "img (HTML tag)", "drawImage method"}} -The `drawImage` method allows us to draw ((pixel)) data onto a -((canvas)). This pixel data can originate from an `` element or -from another canvas. The following example creates a detached `` -element and loads an image file into it. But it cannot immediately -start drawing from this picture because the browser may not have -loaded it yet. To deal with this, we register a `"load"` event handler -and do the drawing after the image has loaded. +El método `drawImage` nos permite dibujar datos de ((pixel))es en +un ((canvas)). Estos datos pueden originarse desde un elemento `` o +desde otro canvas. El siguiente ejemplo crea un elemento `` +y carga un archivo de imagen en él. Pero no podemos empezar a +de esta imagen porquen el navegador podría no haberla cargadao aún. +Para lidiar con esto, registramos un `"load"` _event handler_ +y dibujamos después de que la imagen se ha cargado. ```{lang: "text/html"} @@ -568,42 +568,42 @@ and do the drawing after the image has loaded. {{index "drawImage method", scaling}} -By default, `drawImage` will draw the image at its original size. You -can also give it two additional arguments to set a different width -and height. +Por defecto, `drawImage` dibujará la imagen en su tamaño original. +También puedes darle dos argumentos adicionales para definir +ancho y alto distintos. -When `drawImage` is given _nine_ arguments, it can be used to draw -only a fragment of an image. The second through fifth arguments -indicate the rectangle (x, y, width, and height) in the source image -that should be copied, and the sixth to ninth arguments give the -rectangle (on the canvas) into which it should be copied. +Cuando `drawImage` recibe _nueve_ argumentos, puede usarse para +dibujar solo un fragmento de la imagen. Del segundo al quinto argumentos +indican el rectángulo (x, y, ancho y alto) en la imagen de origen +que debe copiarse, y de los argumentos cinco a nueve indicen el +otro (en el canvas) en donde serán copiados. {{index "player", "pixel art"}} -This can be used to pack multiple _((sprite))s_ (image elements) into -a single image file and then draw only the part you need. For example, -we have this picture containing a game character in multiple +Esto puede ser usado para empaquetar múltiples _((sprite))s_ (elementos de imagen) +en una sola imagen y dibujar solo la parte que necesitas. Por ejemplo, +tenemos una imagen con un personaje de un juego en múltiples ((pose))s: -{{figure {url: "img/player_big.png", alt: "Various poses of a game character",width: "6cm"}}} +{{figure {url: "img/player_big.png", alt: "Varias poses de un personaje",width: "6cm"}}} {{index [animation, "platform game"]}} -By alternating which pose we draw, we can show an animation that -looks like a walking character. +Alternando las poses que dibujamos, podemos mostrar una animación +en la que se vea nuestro personaje caminando. {{index "fillRect method", "clearRect method", clearing}} -To animate a ((picture)) on a ((canvas)), the `clearRect` method is -useful. It resembles `fillRect`, but instead of coloring the -rectangle, it makes it ((transparent)), removing the previously drawn -pixels. +Para animar una ((imagen)) en un ((canvas)), el método `clearRect` es +muy útil. Reutiliza `fillRect`, pero en vez de colorear el +rectángulo, lo vuelve ((transparente)), quitando los pixeles +previamente dibujados. {{index "setInterval function", "img (HTML tag)"}} -We know that each _((sprite))_, each subpicture, is 24 ((pixel))s wide -and 30 pixels high. The following code loads the image and then sets -up an interval (repeated timer) to draw the next ((frame)): +Sabemos que cada _((sprite))_, cada subimagen, es de 24 ((pixele))s de ancho +y 30 pixeles de alto. El siguiente código carga la imagen y establece +un intervalo de tiempo para dibujar el siguiente ((frame)): ```{lang: "text/html"} @@ -613,15 +613,15 @@ up an interval (repeated timer) to draw the next ((frame)): img.src = "img/player.png"; let spriteW = 24, spriteH = 30; img.addEventListener("load", () => { - let cycle = 0; + let ciclo = 0; setInterval(() => { cx.clearRect(0, 0, spriteW, spriteH); cx.drawImage(img, // source rectangle - cycle * spriteW, 0, spriteW, spriteH, + ciclo * spriteW, 0, spriteW, spriteH, // destination rectangle 0, 0, spriteW, spriteH); - cycle = (cycle + 1) % 8; + ciclo = (ciclo + 1) % 8; }, 120); }); @@ -629,11 +629,11 @@ up an interval (repeated timer) to draw the next ((frame)): {{index "remainder operator", "% operator", [animation, "platform game"]}} -The `cycle` binding tracks our position in the animation. For each -((frame)), it is incremented and then clipped back to the 0 to 7 range -by using the remainder operator. This binding is then used to compute -the x-coordinate that the sprite for the current pose has in the -picture. +El valor `ciclo` rastrea la posición en la animación. Para cada +((frame)), se incrementa y regresa al rango del 0 al 7 usando +el operador del remanente. Entonces este valor es usado para +calcular la coordenada en x que tiene el _sprite_ para la pose que +tiene actualmente la imagen. ## Transformation From d74c2453947c550d3d4fb6253d7725af9e60025d Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 6 Nov 2020 13:36:21 -0600 Subject: [PATCH 13/22] =?UTF-8?q?Secci=C3=B3n=20traducida:=20Transformacio?= =?UTF-8?q?nes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 120 ++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index ef0dcef38..e5a30808a 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -641,15 +641,15 @@ tiene actualmente la imagen. {{indexsee flipping, mirroring}} -But what if we want our character to walk to the left instead of to -the right? We could draw another set of sprites, of course. But we can -also instruct the ((canvas)) to draw the picture the other way round. +¿Pero que pasa si quieremos que nuestro personaje camine a la izquierda +en vez de la derecha? Podríamos dibujar otro conjunto de _sprites_... o podríamos +decirle al ((canvas)) que redibuje la ilustración hacia el otro lado. {{index "scale method", scaling}} -Calling the `scale` method will cause anything drawn after it to be -scaled. This method takes two parameters, one to set a horizontal -scale and one to set a vertical scale. +Llamar al método `scale` provocará que todo lo que dibujemos después sea escalado. Este método toma dos parámetros, uno para +establecer la escala horizontal y otro para establecer la +escala vertical. ```{lang: "text/html"} @@ -665,66 +665,66 @@ scale and one to set a vertical scale. {{if book -Because of the call to `scale`, the circle is drawn three times as wide -and half as high. +El resultado de llamar a `scale`, es que el círculo es dibujado con +el triple de ancho y la mitad de alto. -{{figure {url: "img/canvas_scale.png", alt: "A scaled circle",width: "6.6cm"}}} +{{figure {url: "img/canvas_scale.png", alt: "Un círculo escalado",width: "6.6cm"}}} if}} {{index mirroring}} -Scaling will cause everything about the drawn image, including the -((line width)), to be stretched out or squeezed together as specified. -Scaling by a negative amount will flip the picture around. The -flipping happens around point (0,0), which means it will also flip the -direction of the coordinate system. When a horizontal scaling of -1 is -applied, a shape drawn at x position 100 will end up at what used to -be position -100. +Escalar provoca que todo lo que se encuentre sobre la imagen, incluyendo el +((ancho de línea)), y se ajustan de acuerdo a las instrucciones que se dieron. +Escalar en una escala negativa dará vuelta a la figura. El giro +sucede sobre la coordenada (0,0), lo que significa que también gira +la dirección del sistema de coordinadas. Cuando el escalado +horzontal es de -1 es aplicado, una figura en la posición 100 +terminará en lo que era la posición -100. {{index "drawImage method"}} -So to turn a picture around, we can't simply add `cx.scale(-1, 1)` -before the call to `drawImage` because that would move our picture -outside of the ((canvas)), where it won't be visible. You could adjust -the ((coordinates)) given to `drawImage` to compensate for this by -drawing the image at x position -50 instead of 0. Another solution, -which doesn't require the code that does the drawing to know about the -scale change, is to adjust the ((axis)) around which the scaling -happens. +Para girar una imagen, podemos sencillamente agregar `cx.scale(-1, 1)` +antes de llamar `drawImage` porque eso movería nuestra figura +fuera del ((canvas)), donde no será visible. Puedes ajustar +las ((coordenadas)) dadas a `drawImage` para compensar que la +la imagen se dibuja en la posición x -50 en vez de 0. Otra solución, +que no requiere que el código que hace le dibujo necesite saber +sobre el cambio de escala, es ajustar el ((eje)) sobre el que el +escalado sucede. {{index "rotate method", "translate method", transformation}} -There are several other methods besides `scale` that influence the -coordinate system for a ((canvas)). You can rotate subsequently drawn -shapes with the `rotate` method and move them with the `translate` -method. The interesting—and confusing—thing is that these -transformations _stack_, meaning that each one happens relative to the -previous transformations. +Hay otros métodos además de `scale` que influencian el sistema +de coordenadas de un ((canvas)). Puedes rotar las figuras de forma +subsecuente con el método `rotate` y movelos con el método +`translate`. Lo interesante —y confuso— de esto es que las +transformaciones se _apilan_, lo que significa que cada una sucede +de forma relativa a la transformación anterior. {{index "rotate method", "translate method"}} -So if we translate by 10 horizontal pixels twice, everything will be -drawn 20 pixels to the right. If we first move the center of the -coordinate system to (50,50) and then rotate by 20 ((degree))s (about -0.1π ((radian))s), that rotation will happen _around_ point (50,50). +Entonces trasladando por 10 pixeles horizontales dos veces, todo de dibujará +20 pixeles a la derecha. Si primero movemos el centro del sistema +de coordenadas hacia (50,50) y lo rotamos por 20 ((grado))s (alrededor +de 0.1π ((radian))es), la rotación sucederá _alredador_ del punto (50,50). -{{figure {url: "img/transform.svg", alt: "Stacking transformations",width: "9cm"}}} +{{figure {url: "img/transform.svg", alt: "Apilando transformaciones",width: "9cm"}}} {{index coordinates}} -But if we _first_ rotate by 20 degrees and _then_ translate by -(50,50), the translation will happen in the rotated coordinate system -and thus produce a different orientation. The order in which -transformations are applied matters. +Pero si _primero_ rotamos por 20 grados _y entonces_ trasladamos +(50,50), la traslación sucederá en el sistema de coordenadas rotadas +y esto producirá una orientación distinta. El orden en el cual se +aplican las transformaciones importa. {{index axis, mirroring}} -To flip a picture around the vertical line at a given x position, we -can do the following: +Para voltear una imagen alrededor de la línea vertical dada una +posición en x, podemos hacer lo siguiente: ```{includeCode: true} -function flipHorizontally(context, around) { +function voltearHorizontalmente(context, around) { context.translate(around, 0); context.scale(-1, 1); context.translate(-around, 0); @@ -733,26 +733,28 @@ function flipHorizontally(context, around) { {{index "flipHorizontally method"}} -We move the y-((axis)) to where we want our ((mirror)) to be, apply -the mirroring, and finally move the y-axis back to its proper place in -the mirrored universe. The following picture explains why this works: +Recorremos el ((eje))-y donde queramor colocar el ((espejo)), +aplicamos el espejeado, y regresamos el eje-y de regreso a su lugar +en el universo espejeado. la sigiente imagen explica como funciona: -{{figure {url: "img/mirror.svg", alt: "Mirroring around a vertical line",width: "8cm"}}} +{{figure {url: "img/mirror.svg", alt: "Espejeando sobre la línea vertical",width: "8cm"}}} {{index "translate method", "scale method", transformation, canvas}} -This shows the coordinate systems before and after mirroring across -the central line. The triangles are numbered to illustrate each step. -If we draw a triangle at a positive x position, it would, by default, -be in the place where triangle 1 is. A call to `flipHorizontally` -first does a translation to the right, which gets us to triangle 2. It -then scales, flipping the triangle over to position 3. This is not -where it should be, if it were mirrored in the given line. The second -`translate` call fixes this—it "cancels" the initial translation and -makes triangle 4 appear exactly where it should. - -We can now draw a mirrored character at position (100,0) by flipping -the world around the character's vertical center. +Esto muestra el sistema de coordenadas antes y después de espejear +atravesando la línea central. Los tríangulos están numerados para +illustrar cada paso. +Si dibujamos un triángulo en una posición x positiva, debería estar +por defecto en el lugar donde se encuentra el tríangulo 1. Una +llamada a `voltearHorizontalmente` +realiza primero la traslación a la derecha, la cual nos da el +triángulo 2. Cuando lo escala, voltea el triángulo hacia la +posición 3. Esto no debería ser así, si espejeamos en la línea inicial. +La segunda llamada a `translate` corrige esto, "cancelando" la +traslación inicial y el triángulo 4 aparece donde debe. + +Ahora podemos dibujar un personaje en la posición (100,0) girando el +universo alrededor del centro vertical de los personajes. ```{lang: "text/html"} @@ -762,7 +764,7 @@ the world around the character's vertical center. img.src = "img/player.png"; let spriteW = 24, spriteH = 30; img.addEventListener("load", () => { - flipHorizontally(cx, 100 + spriteW / 2); + voltearHorizontalmente(cx, 100 + spriteW / 2); cx.drawImage(img, 0, 0, spriteW, spriteH, 100, 0, spriteW, spriteH); }); From 9ddf2d27bb6423a82bebf93b9b71674aef983e2a Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 6 Nov 2020 15:01:24 -0600 Subject: [PATCH 14/22] =?UTF-8?q?Secci=C3=B3n=20traducida:=20guardando=20t?= =?UTF-8?q?ransformaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 81 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index e5a30808a..435a4b7b9 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -635,7 +635,7 @@ el operador del remanente. Entonces este valor es usado para calcular la coordenada en x que tiene el _sprite_ para la pose que tiene actualmente la imagen. -## Transformation +## Transformaciones {{index transformation, mirroring}} @@ -771,82 +771,81 @@ universo alrededor del centro vertical de los personajes. ``` -## Storing and clearing transformations +## Almacenando y limpiando transformaciones {{index "side effect", canvas, transformation}} -Transformations stick around. Everything else we draw after -((drawing)) that mirrored character would also be mirrored. That might -be inconvenient. +La transformaciones se mantienen. Todo lo que dibujemos después de +((dibujar)) de nuestro personaje espejeado también puede ser +espejeado. Eso puede ser un inconveniente. -It is possible to save the current transformation, do some drawing and -transforming, and then restore the old transformation. This is usually -the proper thing to do for a function that needs to temporarily -transform the coordinate system. First, we save whatever -transformation the code that called the function was using. Then the -function does its thing, adding more transformations on top of the -current transformation. Finally, we revert to the -transformation we started with. +Es posible guardar el estado actual de la transformación, dibujar +de nuevo y restaurar la transformación anterior. Usualmente esto +es lo que debemos hacer para una función que necesite transformar +de forma temporal el sistema de coordenadas. Primero, guardamos el +código de la transformación que haya llamado a la función que estamos +usando. La función hace lo suyo, agregando más transformaiones +sobre la transformación actual. Finalmente, revertimos a la +transformación con la que empezamos. {{index "save method", "restore method", [state, "of canvas"]}} -The `save` and `restore` methods on the 2D ((canvas)) context do this -((transformation)) management. They conceptually keep a stack of -transformation states. When you call `save`, the current state is -pushed onto the stack, and when you call `restore`, the state on top -of the stack is taken off and used as the context's current -transformation. You can also call `resetTransform` to fully reset the -transformation. +Los métodos `save` y `restore` en el contexto del ((canvas)) 2D +realizan el manejo de las ((transformaciones)). Conceptualmente +mantienen una pila de los estados de la transformación. Cuando +se llama `save`, el estado actual se agrega a la pila, y cuando se +llama `restore`, el estado en la cima de la pila se usa en el +contexto actual de la transformación. También puedes llamar +`resetTransform` para resetear por completo la transformación. {{index "branching recursion", "fractal example", recursion}} -The `branch` function in the following example illustrates what you -can do with a function that changes the transformation and then calls -a function (in this case itself), which continues drawing with -the given transformation. +La función `ramificar` en el siguiente ejemplo ilustra lo que puedes +hacer con una función que cambia la transformación y llama una +función (en este caso a sí misma), que continúa dibujando con una +transformación dada. -This function draws a treelike shape by drawing a line, moving the -center of the coordinate system to the end of the line, and calling -itself twice—first rotated to the left and then rotated to the right. -Every call reduces the length of the branch drawn, and the recursion -stops when the length drops below 8. +La función dibuja un árbol dibujando una línea, moviendo el centro +del sistema de coordenadas hacia el final de la línea, y llamándose +a sí misma rotándose a la izquierda y luego a la derecha. +Cada cada llamada reduce el ancho de la rama dibujada, y la +recursión se detiene cuando el ancho es menor a 8. ```{lang: "text/html"} ``` {{if book -The result is a simple fractal. +El resultado es un simple fractal. -{{figure {url: "img/canvas_tree.png", alt: "A recursive picture",width: "5cm"}}} +{{figure {url: "img/canvas_tree.png", alt: "Una imagen recursiva",width: "5cm"}}} if}} {{index "save method", "restore method", canvas, "rotate method"}} -If the calls to `save` and `restore` were not there, the second -recursive call to `branch` would end up with the position and rotation -created by the first call. It wouldn't be connected to the current -branch but rather to the innermost, rightmost branch drawn by the -first call. The resulting shape might also be interesting, but it is -definitely not a tree. +Sí la llamada a `save` y `restore` no estuvieran, la segunda llamada +recursiva a `ramificar` terminarían con la posición y rotación +creados en la primera llamada. No se conectarían con la rama actual, +excepto por las mas cercanas, la mayoría dibujadas en la primera +llamada. La figura resultante sería interesante, pero no es un árbol definitivamente. {{id canvasdisplay}} From 75fa026f03910cd6b765b90f7762c7b5e748fe31 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 11 Nov 2020 16:10:38 -0600 Subject: [PATCH 15/22] =?UTF-8?q?Secci=C3=B3n=20traducida:=20juego?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 158 ++++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 435a4b7b9..54947d0b4 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -849,30 +849,31 @@ llamada. La figura resultante sería interesante, pero no es un árbol definitiv {{id canvasdisplay}} -## Back to the game +## De regreso al juego {{index "drawImage method"}} -We now know enough about ((canvas)) drawing to start working on a -((canvas))-based ((display)) system for the ((game)) from the -[previous chapter](game). The new display will no longer be showing -just colored boxes. Instead, we'll use `drawImage` to draw pictures -that represent the game's elements. +Ahora que sabemos dibujar en el ((canvas)), es tiempo de empezar +a trabajar en el sistema de ((_display_)) basado en el ((canvas)) +para el ((juego)) del [capítulo anterior](game). El nuevo display +no mostrará sólo cajas de colores. En vez de eso, usaremos +`drawImage` para dibujar imágenes que representen los elementos +del juego. {{index "CanvasDisplay class", "DOMDisplay class", [interface, object]}} -We define another display object type called `CanvasDisplay`, -supporting the same interface as `DOMDisplay` from [Chapter -?](game#domdisplay), namely, the methods `syncState` and `clear`. +Definimos otro objeto del _display_ llamado `CanvasDisplay`, +que soporta la misma interfaz que en `DOMDisplay` del [Capítulo +?](game#domdisplay), los métodos `syncState` y `clear`. {{index [state, "in objects"]}} -This object keeps a little more information than `DOMDisplay`. Rather -than using the scroll position of its DOM element, it tracks its own -((viewport)), which tells us what part of the level we are currently -looking at. Finally, it keeps a `flipPlayer` property so that even -when the player is standing still, it keeps facing the direction it -last moved in. +Este objeto posee algo más de información que `DOMDisplay`. En vez +de usar la posición del _scroll_ de su elemento DOM, rastrea su propio +((_viewport_)), que nos dice en que parte del nivel nos encontramos. +Finalmente, hace uso de la propiedas `flipPlayer` de modo que +incluso cuando el jugador se encuentra detenido, lo hace en la última +dirección en que se movió. ```{sandbox: "game", includeCode: true} class CanvasDisplay { @@ -899,8 +900,8 @@ class CanvasDisplay { } ``` -The `syncState` method first computes a new viewport and then draws -the game scene at the appropriate position. +El método `syncState` calcula primero un nuevo _viewport_ y después +dibuja la escena del juego en la posición apropiada. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.syncState = function(state) { @@ -913,19 +914,18 @@ CanvasDisplay.prototype.syncState = function(state) { {{index scrolling, clearing}} -Contrary to `DOMDisplay`, this display style _does_ have to redraw the -background on every update. Because shapes on a canvas are just -((pixel))s, after we draw them there is no good way to move them (or -remove them). The only way to update the canvas display is to clear it -and redraw the scene. We may also have scrolled, which requires the -background to be in a different position. +Contrario al `DOMDisplay`, este estilo de display _tiene que_ redibujar +el fondo en cada _frame_. Esto se debe a que las figuras en un canvas +son solo ((pixel))es, después de que los dibujamos no hay un método +apropiado para movelos (o eliminarlos). La única forma de actualizar +el canvas es limpiarlo y redibujar la escena. También podríamos desplazarlo, +Lo que requiere que el fondo este en una posición diferente. {{index "CanvasDisplay class"}} -The `updateViewport` method is similar to `DOMDisplay`'s -`scrollPlayerIntoView` method. It checks whether the player is too -close to the edge of the screen and moves the ((viewport)) when this -is the case. +El método `updateViewport` es similar al método `scrollPlayerIntoView` +del método `DOMDisplay`. Comprueba si el jugador esta muy cerca +del borde de la pantalla y mueve el ((_viewport_)) cuando asi sea el caso ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.updateViewport = function(state) { @@ -950,14 +950,14 @@ CanvasDisplay.prototype.updateViewport = function(state) { {{index boundary, "Math.max function", "Math.min function", clipping}} -The calls to `Math.max` and `Math.min` ensure that the viewport does -not end up showing space outside of the level. `Math.max(x, 0)` makes -sure the resulting number is not less than zero. `Math.min` -similarly guarantees that a value stays below a given bound. +Las llamadas a `Math.max` y `Math.min` aseguran que el _viewport_ +no termine mostrando espacio fuera del nivel. +`Math.max(x, 0)` aseguramos que el resultado sea mayor a cero. +`Math.min` hacer algo similar al garantizar que un valor se mantenga +por debajo de lo indicado. -When ((clearing)) the display, we'll use a slightly different -((color)) depending on whether the game is won (brighter) or lost -(darker). +Cuando se ((limpia)) el _display_, usamos un ((color)) ligeramente +distinto dependiendo si el jugador ganó (más claro) o perdió (más oscuro). ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.clearDisplay = function(status) { @@ -975,9 +975,9 @@ CanvasDisplay.prototype.clearDisplay = function(status) { {{index "Math.floor function", "Math.ceil function", rounding}} -To draw the background, we run through the tiles that are visible in -the current viewport, using the same trick used in the `touches` -method from the [previous chapter](game#touches). +Para dibujar el fondo, hacemos que los mosaicos sean visibles en el +_viewport_ actual, usando el mismo truco que en el método `touches` +del [capítulo anterior](game#touches). ```{sandbox: "game", includeCode: true} let otherSprites = document.createElement("img"); @@ -1007,46 +1007,47 @@ CanvasDisplay.prototype.drawBackground = function(level) { {{index "drawImage method", sprite, tile}} -Tiles that are not empty are drawn with `drawImage`. The -`otherSprites` image contains the pictures used for elements other -than the player. It contains, from left to right, the wall tile, the -lava tile, and the sprite for a coin. +Los mosaicos que no están vacíos se dibujan con `drawImage`. La +imagen `otherSprites` contiene las imagenes usadas para los elementos +distintos al jugador. Contiene, de izquierda a derecha, el +mosaico del muro, el mosaico de lava, y el _sprite_ de una moneda. -{{figure {url: "img/sprites_big.png", alt: "Sprites for our game",width: "1.4cm"}}} +{{figure {url: "img/sprites_big.png", alt: "Sprites para nuestro juego",width: "1.4cm"}}} {{index scaling}} -Background tiles are 20 by 20 pixels since we will use the same scale -that we used in `DOMDisplay`. Thus, the offset for lava tiles is 20 -(the value of the `scale` binding), and the offset for walls is 0. +Los mosaicos del fondo son de 20 por 20 pixeles dado que usaremos +la misma escala que en `DOMDisplay`. Así, el _offset_ para el +mosaico de lava es de 20 (el valor encapsulado de `scale`), y +el _offset_ para el de muro es de 0. {{index drawing, "load event", "drawImage method"}} -We don't bother waiting for the sprite image to load. Calling -`drawImage` with an image that hasn't been loaded yet will simply do -nothing. Thus, we might fail to draw the game properly for the first -few ((frame))s, while the image is still loading, but that is not a -serious problem. Since we keep updating the screen, the correct scene -will appear as soon as the loading finishes. +No hace falta quedarse esperando a que carge el _sprite_ de la imagen. +Llamar a `drawImage` con una imagen que aún no ha sido cargada, +simplemente no hace anda. Aun así, podría fallar en dibujar el luego +de forma adecuada durante los primeros ((_frame_))s, mientras la +imagen siga cargádose, pero esto no es un problema. Mietras sigamos +actualizando la pantalla, la escena correcta aparecerá tan pronto +la imagen termine de cargarse. {{index "player", [animation, "platform game"], drawing}} -The ((walking)) character shown earlier will be used to represent the -player. The code that draws it needs to pick the right ((sprite)) and -direction based on the player's current motion. The first eight -sprites contain a walking animation. When the player is moving along a -floor, we cycle through them based on the current time. We want to -switch frames every 60 milliseconds, so the ((time)) is divided by 60 -first. When the player is standing still, we draw the ninth sprite. -During jumps, which are recognized by the fact that the vertical speed -is not zero, we use the tenth, rightmost sprite. +El personaje ((caminante)) mostrado antes será usado para representar al +jugador. El código que lo dibuja necesita escoger el ((sprite)) correcto y la +dirección basados en el movimiento actual del jugador. Los primeros ocho +_sprites_ contienen una animación de caminata. Cuando el jugador se mueve sobre +el suelo, los pasamos conforme al tiempo actual. Como queremos +cambiar _frames_ cada 60 millisegundos, entonces el ((tiempo)) es dividido entre 60 +ṕrimero. Cuando el jugador se detiene, dibujamos el noveno _sprite_. +Durante los saltos, que reconoceremos por el hecho de que la velocidad vertical +no es cero, usaremos el décimo _sprite_. {{index "flipHorizontally function", "CanvasDisplay class"}} -Because the ((sprite))s are slightly wider than the player object—24 -instead of 16 pixels to allow some space for feet and arms—the method -has to adjust the x-coordinate and width by a given amount -(`playerXOverlap`). +Ya que los ((_sprite_))s son ligeramente más anchos que el jugador +—24 píxeles en vez de 16 para permitir algo de espacio para +pies y brazos— tenemos que ajustar las coordenadas en x y el ancho por el cantidad dada por (`playerXOverlap`). ```{sandbox: "game", includeCode: true} let playerSprites = document.createElement("img"); @@ -1079,8 +1080,8 @@ CanvasDisplay.prototype.drawPlayer = function(player, x, y, }; ``` -The `drawPlayer` method is called by `drawActors`, which is -responsible for drawing all the actors in the game. +El método `drawPlayer` es llamado por `drawActors`, que es +responsable de dibujar todos los involucrados en el juego. ```{sandbox: "game", includeCode: true} CanvasDisplay.prototype.drawActors = function(actors) { @@ -1101,21 +1102,22 @@ CanvasDisplay.prototype.drawActors = function(actors) { }; ``` -When ((drawing)) something that is not the ((player)), we look at its -type to find the offset of the correct sprite. The ((lava)) tile is -found at offset 20, and the ((coin)) sprite is found at 40 (two times -`scale`). +Cuando se trata de ((dibujar)) algo que no es el ((jugador)), nos fijamos en +el tipo de objeto que es para encontrar el _offset_ del _sprite_ correcto. +El mosaico de ((lava)) se encuentra en un _offset_ de 20, y el _sprite_ +de ((moneda)) se encuentra en 40 (dos veces `scale`). {{index viewport}} -We have to subtract the viewport's position when computing the actor's -position since (0,0) on our ((canvas)) corresponds to the top left of -the viewport, not the top left of the level. We could also have used -`translate` for this. Either way works. +Debemos restar la posición del viewport cuando calculamos la posición +de los involucrados desde la posición (0,0) en nuestro ((canvas)) +correspondiente a la esquina superior izquierda del viewport, +no la esquina superior izquierda del nivel. También podemos hacer +uso de `translate` para esto. Ambos métodos funcionan. {{if interactive -This document plugs the new display into `runGame`: +Este documento se enlaza con el nuevo display en `runGame`: ```{lang: "text/html", sandbox: game, focus: yes, startCode: true} @@ -1131,10 +1133,10 @@ if}} {{index [game, screenshot], [game, "with canvas"]}} -That concludes the new ((display)) system. The resulting game looks -something like this: +Con eso terminamos el nuevo sistema de ((display)). El luego resultante +luce como algo así: -{{figure {url: "img/canvas_game.png", alt: "The game as shown on canvas",width: "8cm"}}} +{{figure {url: "img/canvas_game.png", alt: "El juego se muestra en el canvas",width: "8cm"}}} if}} From 9bf26687fb58647efcecc91682763459b9c03024 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 14 Nov 2020 12:56:59 -0600 Subject: [PATCH 16/22] =?UTF-8?q?Secci=C3=B3n=20traducida:=20Escogiendo=20?= =?UTF-8?q?una=20interfaz...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 79 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 54947d0b4..df0fc775c 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1142,65 +1142,66 @@ if}} {{id graphics_tradeoffs}} -## Choosing a graphics interface +## Escogiendo una interfaz gráfica -So when you need to generate graphics in the browser, you can choose -between plain HTML, ((SVG)), and ((canvas)). There is no single -_best_ approach that works in all situations. Each option has -strengths and weaknesses. +Cuando se necesita generar gráficos en el navegador, se puede escoger +entre HTML plano, ((SVG)), y ((canvas)). No existe un enfoque que +funcione mejor en cualquier situación. Cada opción tiene sus pros +y sus contras. {{index "text wrapping"}} -Plain HTML has the advantage of being simple. It also integrates well -with ((text)). Both SVG and canvas allow you to draw text, but they -won't help you position that text or wrap it when it takes up more -than one line. In an HTML-based picture, it is much easier to include -blocks of text. +El HTML plano tiene la ventaja de ser simple. También puede integrar +((texto)). Tanto el SVG como el canvas permiten dibujar texto, pero +no te ayudarán si necesitas cambiar la posición o acomodarlo cuando +necesites más de una línea. En una imagen basada en HTML es mucho +más fácil incluir bloques de texto. {{index zooming, SVG}} -SVG can be used to produce ((crisp)) ((graphics)) that look good at -any zoom level. Unlike HTML, it is designed for drawing -and is thus more suitable for that purpose. +SVG puede usarse para generar ((gráficos)) ((claros)) que se ven +bien con cualquier nivel de zoom. a diferencia del HTML, está diseñado +para dibjar y es más adecuado a para ese propósito. {{index [DOM, graphics], SVG, "event handling", ["data structure", tree]}} -Both SVG and HTML build up a data structure (the DOM) that -represents your picture. This makes it possible to modify elements -after they are drawn. If you need to repeatedly change a small part of -a big ((picture)) in response to what the user is doing or as part of -an ((animation)), doing it in a canvas can be needlessly expensive. -The DOM also allows us to register mouse event handlers on every -element in the picture (even on shapes drawn with SVG). You can't do -that with canvas. +Tanto SVG como HTML contienen una estrucura de datos (el DOM) que +representa tu imagen. Esto hace posible modificar elementos después +después de que son dibujados. Si necesitas cambiar de forma repetida +una pequeña parte de una ((imagen)) grande como respuesta a lo que +el usuario este haciendo o como parte de una ((animación)), hacerlo +en un canvas puede ser innecesariamente difícil. +El DOM también nos permite registrar los _event handlers_ del mouse +en cada elemento en la imagen (incluso en figuras dibujadas en SVG), cosa que no se puede hacer con el canvas. {{index performance, optimization}} -But ((canvas))'s ((pixel))-oriented approach can be an advantage when -drawing a huge number of tiny elements. The fact that it does not -build up a data structure but only repeatedly draws onto the same -pixel surface gives canvas a lower cost per shape. +Pero el enfoque orientado a ((pixeles)) del ((canvas)) puede ser una ventaja +cuando se trata de dibujar un gran número de elementos pequeños. +El hecho de que no posea una estructura de datos sino únicamente de +dibujar de forma repetida sobre la misma superficie de pixeles le +da al canvas un costo menor por figura. {{index "ray tracer"}} -There are also effects, such as rendering a scene one pixel at a time -(for example, using a ray tracer) or postprocessing an image with -JavaScript (blurring or distorting it), that can be realistically -handled only by a ((pixel))-based approach. +También se pueden agregar efectos, como renderizar una escena con un pixel +a la vez (por ejemplo, usando trazado de rayos) o postprocesar una +imagen con JavaScript (agregando _blur o distorsionándola_), que +puede hacerse de forma realista usando un enfoque basados en pizeles. -In some cases, you may want to combine several of these techniques. -For example, you might draw a ((graph)) with ((SVG)) or ((canvas)) but -show ((text))ual information by positioning an HTML element on top -of the picture. +En algunso casos, podrías intentar combinar varias de estas técnicas. +Por ejemplo, podrías dibujar un ((gráfico)) con ((SVG)) o ((canvas)) pero +mostral información con ((texto)) posicionando un elemnto HTML en +la parte superior de la imagen. {{index display}} -For nondemanding applications, it really doesn't matter much which -interface you choose. The display we built for our game in this -chapter could have been implemented using any of these three -((graphics)) technologies since it does not need to draw text, handle -mouse interaction, or work with an extraordinarily large number of -elements. +Para aplicaciones de baja demanda, no importa mucho que interfaz +escogas. El display que construimos para nuestro juego en este +capítulo podría haberse hecho implementando cual sea de estas tres +tecnologías de ((gráficos)) dado que no necesitamos dibujar texto, +manejar interacciones del mouse o trabajar con un numero de elementos +extraordinariamente grande. ## Summary From a365740a3ea6bfd7b611660d0acf1ac6f9b5f533 Mon Sep 17 00:00:00 2001 From: Alberto Date: Sat, 14 Nov 2020 22:58:05 -0600 Subject: [PATCH 17/22] =?UTF-8?q?Secci=C3=B3n=20traducida:=20resumen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 69 ++++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index df0fc775c..fcb4bd675 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1203,47 +1203,48 @@ tecnologías de ((gráficos)) dado que no necesitamos dibujar texto, manejar interacciones del mouse o trabajar con un numero de elementos extraordinariamente grande. -## Summary +## Resumen -In this chapter we discussed techniques for drawing graphics in the -browser, focusing on the `` element. +En este capítulo discutimos técnicas para dibujo de gráficos en el +navegador, enfoncándonos en el elemento ``. -A canvas node represents an area in a document that our program may -draw on. This drawing is done through a drawing context object, -created with the `getContext` method. +Un nodo de canvas representa un área en un documento en el que nuestro +programa puede dibujar. Este dibujo se hace a través de objetos de +contexto de dibujo, usando el método `getContext`. -The 2D drawing interface allows us to fill and stroke various shapes. -The context's `fillStyle` property determines how shapes are filled. -The `strokeStyle` and `lineWidth` properties control the way lines are -drawn. +La interfaz de dibujo 2D nos permite rellenar y delineas varias figuras. +La propiedad `fillStyle` del contexto determina como las figuras son rellenadas. +Las propiedades `strokeStyle` y `lineWidth` controlan la manera en +que las líneas se dibujan. -Rectangles and pieces of text can be drawn with a single method call. -The `fillRect` and `strokeRect` methods draw rectangles, and the -`fillText` and `strokeText` methods draw text. To create custom -shapes, we must first build up a path. +Los rectángulos y textos se pueden dibujar con una simple llamada de método. +Los métodos `fillRect` y `strokeRect` dibujan rectángulos y los +métodos `fillText` y `strokeText` dibujan texto. Para crear +figuras a medida, primero debemos crear una ruta. {{index stroking, filling}} -Calling `beginPath` starts a new path. A number of other methods add -lines and curves to the current path. For example, `lineTo` can add a -straight line. When a path is finished, it can be filled with the -`fill` method or stroked with the `stroke` method. - -Moving pixels from an image or another canvas onto our canvas is done -with the `drawImage` method. By default, this method draws the whole -source image, but by giving it more parameters, you can copy a -specific area of the image. We used this for our game by copying -individual poses of the game character out of an image that contained -many such poses. - -Transformations allow you to draw a shape in multiple orientations. A -2D drawing context has a current transformation that can be changed -with the `translate`, `scale`, and `rotate` methods. These will affect -all subsequent drawing operations. A transformation state can be saved -with the `save` method and restored with the `restore` method. - -When showing an animation on a canvas, the `clearRect` method can be -used to clear part of the canvas before redrawing it. +Llamar a `beginPath` empieza una nueva ruta. Otra serie de métodos +agregan líneas y curvas a la ruta actual. Por ejemplo `lineTo` +puede dibujar una línea reacta. Cuando la ruta de termina, puede +rellenarse con el método `fill` o delinearse con el método `stroke`. + +Mover pixeles desde una imagen o desde un canvas a otro puede hacerse +con el método `drawImage`. Por defecto, este método dibuja la imagen +fuente completa, pero dándole parámetros, puedes copiar un área +específica de la imagen. La usamos para nuestro juego copiando poses +individuales del personajes salidas de una imagen que contiene +muchas poses. + +Las transformaciones te permiten dibujar una figura en múltiples orientaciones +un contexto de dibujo 2D tiene transformaciones que se pueden usar +con los métodos `translate`, `scale` y `rotate`. Estos afectarán +todas las operaciones de dibujo subsecuentes. El estado de una +transformación se puede guardar con el método `save` y restaurar +con el método `restore`. + +Cuando se muestra una animación en un canvas, el método `clearRect` +puede usarse para limpiar parte del canvas antes de redibujarlo. ## Exercises From 9d3c8371875fba9c9cd4a2d0a3c906bcf0ed55bc Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 17 Nov 2020 14:05:01 -0600 Subject: [PATCH 18/22] =?UTF-8?q?Subsecci=C3=B3n=20traducida:=20figuras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 93 +++++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index fcb4bd675..4faf72770 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1071,7 +1071,7 @@ CanvasDisplay.prototype.drawPlayer = function(player, x, y, this.cx.save(); if (this.flipPlayer) { - flipHorizontally(this.cx, x + width / 2); + voltearHorizontalmente(this.cx, x + width / 2); } let tileX = tile * width; this.cx.drawImage(playerSprites, tileX, 0, width, height, @@ -1246,38 +1246,39 @@ con el método `restore`. Cuando se muestra una animación en un canvas, el método `clearRect` puede usarse para limpiar parte del canvas antes de redibujarlo. -## Exercises +## Ejercicios -### Shapes +### Figuras {{index "shapes (exercise)"}} -Write a program that draws the following ((shape))s on a ((canvas)): +Escribe un programa que dibuje las siguientes ((figura))s en un ((canvas)): {{index rotation}} -1. A ((trapezoid)) (a ((rectangle)) that is wider on one side) +1. Un ((trapezoide)) (un ((rectángle)) que es más inclinado de un lado). -2. A red ((diamond)) (a rectangle rotated 45 degrees or ¼π radians) +2. Un ((diamante)) rojo (un rentángulo rotado 45 grados o ¼π radianes). -3. A zigzagging ((line)) +3. Una ((línea)) zigzaggeada. -4. A ((spiral)) made up of 100 straight line segments +4. Una ((espiral)) hecha de 100 segmentos de línea. -5. A yellow ((star)) +5. Una ((estrella)) amarilla. -{{figure {url: "img/exercise_shapes.png", alt: "The shapes to draw",width: "8cm"}}} +{{figure {url: "img/exercise_shapes.png", alt: "Las figuras a dibujar",width: "8cm"}}} -When drawing the last two, you may want to refer to the explanation of -`Math.cos` and `Math.sin` in [Chapter ?](dom#sin_cos), which describes -how to get coordinates on a circle using these functions. +Cuando dibujes las últimas dos, quizás te interese ver las explicaciones +de `Math.cos` y `Math.sin` en el [capítulo ?](dom#sin_cos), que +describe como obtener coordenadas en un círculo usando estas funciones. {{index readability, "hard-coding"}} -I recommend creating a function for each shape. Pass the position, and -optionally other properties such as the size or the number of points, -as parameters. The alternative, which is to hard-code numbers all over -your code, tends to make the code needlessly hard to read and modify. +Recomiendo crear una función para cada figura. Pasa la prporción y +optionalmente otras propiedades como el tamaño o el número de puntos +como parámetros. La alternativa, que requiere escribir una numerología +complicada, tiende a hacer el código innecesariamente difícil de +leer y modificar. {{if interactive @@ -1286,7 +1287,7 @@ your code, tends to make the code needlessly hard to read and modify. ``` @@ -1296,43 +1297,45 @@ if}} {{index [path, canvas], "shapes (exercise)"}} -The ((trapezoid)) (1) is easiest to draw using a path. Pick suitable -center coordinates and add each of the four corners around the center. +El ((trapezoide)) (1) es el más sencillo de dibujar usando una ruta. +Escoge unas coordenadas para el centro y agrega una de las esquinas +alrededor del centro. {{index "flipHorizontally function", rotation}} -The ((diamond)) (2) can be drawn the straightforward way, with a path, -or the interesting way, with a `rotate` ((transformation)). To use -rotation, you will have to apply a trick similar to what we did in the -`flipHorizontally` function. Because you want to rotate around the -center of your rectangle and not around the point (0,0), you must -first `translate` to there, then rotate, and then translate back. - -Make sure you reset the transformation after drawing any shape that -creates one. +El ((diamante)) (2) puede dibujarse de la forma simple, con una ruta, +o de la forma interesante, con una ((transformación)) `rotate`. +Para usar rotación, tendrás que usar un truco similar al que usamos +con la función `voltearHorizontalmente`. Ya que lo que se quiere +es rotar alrededor del centro de su rectángulo y no alrededor del +punto (0,0), deberías primero usar `translate` ahí, luego rotar, y +trasladar de regreso. +Asegúrate de reiniciar la transformación después de dibujar cualquier +figura que se haya creado. {{index "remainder operator", "% operator"}} -For the ((zigzag)) (3) it becomes impractical to write a new call to -`lineTo` for each line segment. Instead, you should use a ((loop)). -You can have each iteration draw either two ((line)) segments (right -and then left again) or one, in which case you must use the evenness -(`% 2`) of the loop index to determine whether to go left or right. +Para el ((zigzag)) (3) se vuelve impráctico escribir una nueva llamada +a `lineTo` para cada línea de segmento. En vez de eso, deberías +usar un ((loop)). Puedes tener una iteración de dibujo por cada dos +segmentos de ((línea))s (derecha y luego izquierda de nuevo) o uno, +en cuyo caso deberías usar el operador (`% 2`) del índice del loop +para determinar la dirección a la derecha o a la izquierda. -You'll also need a loop for the ((spiral)) (4). If you draw a series -of points, with each point moving further along a circle around the -spiral's center, you get a circle. If, during the loop, you vary the -radius of the circle on which you are putting the current point and go -around more than once, the result is a spiral. +También necesitarás usar un loop para el ((espiral)) (4). Sí dibujas +una serie de puntos, con cada punto moviéndose en un círculo alrededor +del centro de la espiral, obtienes un círculo. Sí durante el loop +varías el radio del círculo en el que colocas el punto y lo repites, +el resultado es una espiral. {{index "quadraticCurveTo method"}} -The ((star)) (5) depicted is built out of `quadraticCurveTo` lines. -You could also draw one with straight lines. Divide a circle into -eight pieces for a star with eight points, or however many pieces you -want. Draw lines between these points, making them curve toward the -center of the star. With `quadraticCurveTo`, you can use the center as -the control point. +La ((estrella)) (5) se puede dibujar a partir de las líneas de `quadraticCurveTo`. +También podrías dibujar una con líneas gruesas. Divide un círculo +en ocho piezas para una estrella de ocho puntos, o cuantas piezas +quieras. Dibuja líneas entre estos puntos, haciéndolas curvas alrededor +del centro de la estrella. Con `quadraticCurveTo`, puedes hacer uso +del centro como punto de control. hint}} From 9cb7a9ee9de05b901c0453f65d5b16b8adfa9f54 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 17 Nov 2020 14:41:55 -0600 Subject: [PATCH 19/22] =?UTF-8?q?Subsecci=C3=B3n=20traducida:=20gr=C3=A1fi?= =?UTF-8?q?ca=20de=20pastel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 61 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 4faf72770..6c8d0cc70 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1341,19 +1341,19 @@ hint}} {{id exercise_pie_chart}} -### The pie chart +### La gráfica de pastel {{index label, text, "pie chart example"}} -[Earlier](canvas#pie_chart) in the chapter, we saw an example program -that drew a pie chart. Modify this program so that the name of each -category is shown next to the slice that represents it. Try to find a -pleasing-looking way to automatically position this text that would -work for other data sets as well. You may assume that categories are -big enough to leave ample room for their labels. +[Anteriormente](canvas#pie_chart) en este capítulo, vimos el ejemplo +de un programa que dibuja una gráfica de pastel. Modifica este programa +de forma que el nombre de cada categoría este al lado de la sección +que representa. Trata de encontrar una forma que se posicione automáticamente +de forma adecuada que funcione con otros conjuntos de datos. Puedes +asumir que esas categorías son lo suficientemente amplias para las etiquetas. -You might need `Math.sin` and `Math.cos` again, which are described in -[Chapter ?](dom#sin_cos). +Podrías necesitar `Math.sin` y `Math.cos` de nuevo, las cuales se +describen en el [Capítulo ?](dom#sin_cos). {{if interactive @@ -1366,7 +1366,8 @@ You might need `Math.sin` and `Math.cos` again, which are described in let currentAngle = -0.5 * Math.PI; let centerX = 300, centerY = 150; - // Add code to draw the slice labels in this loop. + // Agrega código en este loop para dibujar las etiquetas + // de las secciones for (let result of results) { let sliceAngle = (result.count / total) * 2 * Math.PI; cx.beginPath(); @@ -1386,19 +1387,19 @@ if}} {{index "fillText method", "textAlign property", "textBaseline property", "pie chart example"}} -You will need to call `fillText` and set the context's `textAlign` and -`textBaseline` properties in such a way that the text ends up where -you want it. +Necesitarás llamar `fillText` y establecer el contexto de `textAlign` +y las propiedades de `textBaseline` de manera que el texto termine +donde quieres. -A sensible way to position the labels would be to put the text on the -line going from the center of the pie through the middle of the slice. -You don't want to put the text directly against the side of the pie -but rather move the text out to the side of the pie by a given number -of pixels. +Una manera sensible de posicionar las etiquetas podría ser poner el +texto en la línea desde el centro de la gráfica a través de la mitad +de la sección. +No queremos que el texto quede directamente al lado de la gráfica, +sino moverlo al lado de la gráfica por unos cuantos píxeles. -The ((angle)) of this line is `currentAngle + 0.5 * sliceAngle`. The -following code finds a position on this line 120 pixels from the -center: +El ((ángulo)) de está línea es `currentAngle + 0.5 * sliceAngle`. +El siguiente código encuentra una posición en esta línea 120 píxels +desde el centro: ```{test: no} let middleAngle = currentAngle + 0.5 * sliceAngle; @@ -1406,19 +1407,17 @@ let textX = Math.cos(middleAngle) * 120 + centerX; let textY = Math.sin(middleAngle) * 120 + centerY; ``` -For `textBaseline`, the value `"middle"` is probably appropriate when -using this approach. What to use for `textAlign` depends on which side -of the circle we are on. On the left, it should be `"right"`, and on -the right, it should be `"left"`, so that the text is positioned away -from the pie. +Para `textBaseline`, el valor `"middle"` es muy apropiado cuando +se quiere usar este enfoque. Para lo que se use `textAlign` depende +de que lado del círculo este. A la izquierda, deberá ser `"right"`, a la derecha, deberá ser `"left"`, para que el texto se coloque lejos de la gráfica. {{index "Math.cos function"}} -If you are not sure how to find out which side of the circle a given -angle is on, look to the explanation of `Math.cos` in [Chapter -?](dom#sin_cos). The cosine of an angle tells us which x-coordinate it -corresponds to, which in turn tells us exactly which side of the -circle we are on. +Si no estamos seguros de como encontrar el lado del círculo dado +un ángulo, mira la explicación de `Math.cos` en el [Capítulo +?](dom#sin_cos). El coseno de un ángulo nos dice en que coordenada +en x le corresponde para saber exactamente en que lado del círculo +nos encontramos. hint}} From a1e733859796ad4f79aefd109704cd14dd023527 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 17 Nov 2020 14:53:50 -0600 Subject: [PATCH 20/22] =?UTF-8?q?Subsecci=C3=B3n=20traducida:=20bal=C3=B3n?= =?UTF-8?q?=20botador?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 6c8d0cc70..a91eca7cd 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1421,14 +1421,14 @@ nos encontramos. hint}} -### A bouncing ball +### Un balón botador {{index [animation, "bouncing ball"], "requestAnimationFrame function", bouncing}} -Use the `requestAnimationFrame` technique that we saw in [Chapter -?](dom#animationFrame) and [Chapter ?](game#runAnimation) to draw a -((box)) with a bouncing ((ball)) in it. The ball moves at a constant -((speed)) and bounces off the box's sides when it hits them. +Usar la técnica de `requestAnimationFrame` que vimos en el [Capítulo +?](dom#animationFrame) y [Chapter ?](game#runAnimation) para dibujar una +((caja)) con un ((balón)) botando en el. El balón se mueve a una +((velocidad)) constante y bota fuera de la caja cuando golpea un borde de la caja. {{if interactive @@ -1448,7 +1448,7 @@ Use the `requestAnimationFrame` technique that we saw in [Chapter requestAnimationFrame(frame); function updateAnimation(step) { - // Your code here. + // Tu código va aquí. } ``` @@ -1459,26 +1459,27 @@ if}} {{index "strokeRect method", animation, "arc method"}} -A ((box)) is easy to draw with `strokeRect`. Define a binding that -holds its size or define two bindings if your box's width and height -differ. To create a round ((ball)), start a path and call `arc(x, y, -radius, 0, 7)`, which creates an arc going from zero to more than a -whole circle. Then fill the path. +Una ((caja)) es fácil de dibujar con `strokeRect`. Define los bordes +para su tamaño o define dos bordes si el ancho y largo de la caja +son distintos. Para crear el ((balón)), empieza una ruta y llama +`arc(x, y, radius, 0, 7)`, que crea un arco desde cero hasta algo +más de un círculo entero. Entonces rellena la ruta. {{index "collision detection", "Vec class"}} -To model the ball's position and ((speed)), you can use the `Vec` -class from [Chapter ?](game#vector)[ (which is available on this -page)]{if interactive}. Give it a starting speed, preferably one that -is not purely vertical or horizontal, and for every ((frame)) multiply -that speed by the amount of time that elapsed. When the ball gets -too close to a vertical wall, invert the x component in its speed. -Likewise, invert the y component when it hits a horizontal wall. +Para modelar la posición y ((velocidad)), puedes usar la clase `Vec` +del [Capítulo ?](game#vector)[ (que está disponible en está página +)]{if interactive}. Dada una velocidad inicial, preferentemente uno +que no es puramente vertical o horizontal, y para cada ((frame)) +multiplica esa velocidad por el monto de tiempo que transcurra. +Cuando el balón esté cerca de un muro vertical, invierte el componente x +en su velocidad. De manera similar, invierte el componente y cuando +golpee un muro horizontal. {{index "clearRect method", clearing}} -After finding the ball's new position and speed, use `clearRect` to -delete the scene and redraw it using the new position. +Después de encontrar la nueva posición y velocidad del balón, usa +`clearRect` para borrar la escena y redibujarla usando la nueva posición. hint}} From dc7b078f7adb08a1c71e1de97a67e2893721d9ac Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 17 Nov 2020 15:19:56 -0600 Subject: [PATCH 21/22] =?UTF-8?q?Subsecci=C3=B3n=20traducida:=20reflejo=20?= =?UTF-8?q?precalculado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 48 +++++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index a91eca7cd..35b60d658 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -1483,42 +1483,44 @@ Después de encontrar la nueva posición y velocidad del balón, usa hint}} -### Precomputed mirroring +### Reflejo precalculado {{index optimization, "bitmap graphics", mirror}} -One unfortunate thing about ((transformation))s is that they slow down -the drawing of bitmaps. The position and size of each ((pixel)) has to be -transformed, and though it is possible that ((browser))s will get -cleverer about transformation in the ((future)), they currently cause a -measurable increase in the time it takes to draw a bitmap. +Una cosa desafortunada sobre la ((transformacion))es que ralentizan +el dibujado de mapa de bits. La posición y tamaño de cada ((pixel)) +tiene que ser transformada e incluso es posible que los ((navegador))es +estén listos para la transformación en el ((futuro)), actualmente +causa un incremento considerable en el tiempo que toma dibujar un +mapa de bits. -In a game like ours, where we are drawing only a single transformed -sprite, this is a nonissue. But imagine that we need to draw hundreds -of characters or thousands of rotating particles from an explosion. +En un juego como el nuestro, donde dibujamos un simple sprite transformado +que no es un gran problema. Pero imagina que necesitamos dibujar +cientos de personajes o miles de partículas rotatorias de una explosión. -Think of a way to allow us to draw an inverted character without -loading additional image files and without having to make transformed -`drawImage` calls every frame. +Piensa en una forma de permitirnos dibujar personajes sin cargar +imágenes adiciones y sin tener que transformar las llamadas de +`drawImage` cada frame. {{hint {{index mirror, scaling, "drawImage method"}} -The key to the solution is the fact that we can use a ((canvas)) -element as a source image when using `drawImage`. It is possible to -create an extra `` element, without adding it to the document, -and draw our inverted sprites to it, once. When drawing an actual -frame, we just copy the already inverted sprites to the main canvas. +La clave de la solución es el hecho de que podemos hacer uso de un elemento +((canvas)) como fuente de la image cuando usemos `drawImage`. Es +posible crear un elemento ``, sin agregarlos al documento, +y dibujar nuestros sprites invertidos en ello. Cuando dibujamos un +frame, en realidad solo copiamos los sprites invertidos al canvas principal. {{index "load event"}} -Some care would be required because images do not load instantly. We -do the inverted drawing only once, and if we do it before the image -loads, it won't draw anything. A `"load"` handler on the image can be -used to draw the inverted images to the extra canvas. This canvas can -be used as a drawing source immediately (it'll simply be blank until -we draw the character onto it). +Podríamos necesitar algunas precauciones para evitar que las imágenes +se carguen de forma instantánea. Hacemos el dibujo invertido una +sola vez, y si lo hacemos antes de que cargue la imagen, no dibujará nada. +Un handler `"load"` en la imagen puede usarse para dibujar imágenes +invertidas al canvas extra. Este canvas puede ser usado como una +imagen fuente (solo aparecerá en blanco hasta que dibujemos un +personaje en él). hint}} From f0bef5e1cd4e1e6de58c2ffae5631ab42da2caeb Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 18 Nov 2020 12:47:18 -0600 Subject: [PATCH 22/22] =?UTF-8?q?Revisi=C3=B3n=20ortogr=C3=A1fica=20y=20re?= =?UTF-8?q?formato?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 17_canvas.md | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/17_canvas.md b/17_canvas.md index 35b60d658..e9f29b1d8 100644 --- a/17_canvas.md +++ b/17_canvas.md @@ -42,7 +42,7 @@ intefaz de programción para dibujar ((forma))s en el espacio del nodo. La principal diferencia entre un canvas y una imagen SVG es que en SVG la descripción original de las figuras es preservada de manera que puedan ser movidas o reescaladas en cualquier momento. Un canvas, -por otro lado, convierte las figuras a ((pixele))s (puntos de color en +por otro lado, convierte las figuras a ((pixel))es (puntos de color en una rejilla) tan pronto son dibujadas y no recuerda cuáles pixeles representa. La única forma de mover una figura en un canvas es limpíando el canvas (o la parte del canvas alrededor de la figura) y redibujarlo @@ -72,8 +72,7 @@ El atributo `xmlns` cambia un elemento (y sus hijos) a un _XML namespace_ diferente. Este _namespace_, identificado por una ((URL)), especifica el dialecto que estamos usando. las etiquetas `` y ``, —que no existen en HTML, pero tienen un significado en -SVG— dibujan formas usando el estilo y posición especificados por sus -atributos. +SVG— dibujan formas usando el estilo y posición especificados por sus atributos. {{if book @@ -119,8 +118,8 @@ gráficos de tres dimensiones mediante la intefaz de OpenGL. Este libro no abordará WebGL —nos limitaremos a dos dimensiones—. Pero si de verdad te interesan los gráficos tridimensionales, te recomiendo que investigues sobre WebGL. Provee una interfaz directa hacia hardware de -gráficos y te permite renderizar escenarios complicados de forma eficiente -usando JavaScript. +gráficos y te permite renderizar escenarios complicados de forma +eficiente usando JavaScript. {{index "getContext method", [canvas, context]}} @@ -140,12 +139,12 @@ elemento `` del DOM. ``` Después de crear el objeto contexto, el ejemplo dibuja un -((rectangulo)) rojo de 100 ((pixel))es de ancho y 50 pixeles de alto con su esquina superior izquierda +((rectángulo)) rojo de 100 ((pixel))es de ancho y 50 pixeles de alto con su esquina superior izquierda en las coordenadas (10,10). {{if book -{{figure {url: "img/canvas_fill.png", alt: "Un canvas con un rectangulo",width: "2.5cm"}}} +{{figure {url: "img/canvas_fill.png", alt: "Un canvas con un rectángulo",width: "2.5cm"}}} if}} @@ -164,15 +163,13 @@ la esquina superior izquierda. En la interfaz del ((canvas)), una figura puede ser _rellenada_ dado un cierto color o diseño, o puede ser _delimtada_, que -significa una ((linea)) dibujada en los bordes. La misma terminología aplica -para SVG. +significa una ((línea)) dibujada en los bordes. La misma terminología aplica para SVG. {{index "fillRect method", "strokeRect method"}} El método `fillRect` rellena un ((rectángulo)). Usa primero las ((coordenadas)) `x` e `y` desde la esquina superior izquierda del rectángulo, después su dimensiones de ancho -y alto. Un método parecido `strokeRect`, dibuja los -((bordes)) del rectángulo. +y alto. Un método parecido `strokeRect`, dibuja los ((bordes)) del rectángulo. {{index [state, "of canvas"]}} @@ -184,8 +181,8 @@ propiedades del contexto del objeto. {{index filling, "fillStyle property"}} La propiedad `fillStyle` controla La manera que se rellenan las figuras. Puede ser -estableciendo una cadena que especifique un ((color)), usando la misma notación -que en ((CSS)). +estableciendo una cadena que especifique un ((color)), usando la +misma notación que en ((CSS)). {{index stroking, "line width", "strokeStyle property", "lineWidth property", canvas}} @@ -1322,9 +1319,9 @@ segmentos de ((línea))s (derecha y luego izquierda de nuevo) o uno, en cuyo caso deberías usar el operador (`% 2`) del índice del loop para determinar la dirección a la derecha o a la izquierda. -También necesitarás usar un loop para el ((espiral)) (4). Sí dibujas +También necesitarás usar un loop para el ((espiral)) (4). Si dibujas una serie de puntos, con cada punto moviéndose en un círculo alrededor -del centro de la espiral, obtienes un círculo. Sí durante el loop +del centro de la espiral, obtienes un círculo. Si durante el loop varías el radio del círculo en el que colocas el punto y lo repites, el resultado es una espiral. @@ -1494,21 +1491,21 @@ estén listos para la transformación en el ((futuro)), actualmente causa un incremento considerable en el tiempo que toma dibujar un mapa de bits. -En un juego como el nuestro, donde dibujamos un simple sprite transformado -que no es un gran problema. Pero imagina que necesitamos dibujar +En un juego como el nuestro, donde dibujamos un simple sprite transformado, +esto no es un gran problema. Pero imagina que necesitamos dibujar cientos de personajes o miles de partículas rotatorias de una explosión. Piensa en una forma de permitirnos dibujar personajes sin cargar imágenes adiciones y sin tener que transformar las llamadas de -`drawImage` cada frame. +`drawImage` en cada frame. {{hint {{index mirror, scaling, "drawImage method"}} La clave de la solución es el hecho de que podemos hacer uso de un elemento -((canvas)) como fuente de la image cuando usemos `drawImage`. Es -posible crear un elemento ``, sin agregarlos al documento, +((canvas)) como fuente de la imagen cuando usemos `drawImage`. Es +posible crear un elemento `` sin agregarlo al documento y dibujar nuestros sprites invertidos en ello. Cuando dibujamos un frame, en realidad solo copiamos los sprites invertidos al canvas principal. @@ -1518,7 +1515,7 @@ Podríamos necesitar algunas precauciones para evitar que las imágenes se carguen de forma instantánea. Hacemos el dibujo invertido una sola vez, y si lo hacemos antes de que cargue la imagen, no dibujará nada. Un handler `"load"` en la imagen puede usarse para dibujar imágenes -invertidas al canvas extra. Este canvas puede ser usado como una +invertidas en el canvas extra. Este canvas puede ser usado como una imagen fuente (solo aparecerá en blanco hasta que dibujemos un personaje en él).