@@ -31,9 +31,9 @@ materials, ((skill)), or talent. You just start smearing.
3131
3232{{index drawing, "select (HTML tag)", "canvas (HTML tag)", component}}
3333
34- The interface for the drawing program shows a big ` <canvas> ` element
35- on top, with a number of form ((field))s below it. The user draws on
36- the ((picture)) by selecting a tool from a ` <select> ` field and then
34+ The interface for the application shows a big ` <canvas> ` element on
35+ top, with a number of form ((field))s below it. The user draws on the
36+ ((picture)) by selecting a tool from a ` <select> ` field and then
3737clicking or dragging across the canvas. There are ((tool))s for
3838drawing single pixels or rectangles, for filling an area, and for
3939picking a color from the picture.
@@ -350,7 +350,8 @@ events, and making sure we call `preventDefault` on the `"touchstart"`
350350event to prevent ((panning)).
351351
352352``` {includeCode: true}
353- PictureCanvas.prototype.touch = function(startEvent, onDown) {
353+ PictureCanvas.prototype.touch = function(startEvent,
354+ onDown) {
354355 let rect = this.dom.getBoundingClientRect();
355356 let pos = pointerPosition(startEvent.touches[0], rect);
356357 let onMove = onDown(pos);
@@ -876,8 +877,8 @@ const baseControls = [ToolSelect, ColorSelect, SaveButton,
876877 LoadButton, UndoButton];
877878
878879function startPixelEditor({state=startState,
879- tools=baseTools,
880- controls=baseControls}) {
880+ tools=baseTools,
881+ controls=baseControls}) {
881882 let app = new PixelEditor(state, {
882883 tools,
883884 controls,
@@ -1025,6 +1026,77 @@ dispatch the appropriate update.
10251026
10261027hint}}
10271028
1029+ ### Efficient drawing
1030+
1031+ During regular drawing, the majority of work that our application does
1032+ happens in ` drawPicture ` . Creating a new state and updating the rest
1033+ of the DOM isn't very expensive, but repainting all the pixels is
1034+ quite a bit of work.
1035+
1036+ Find a way to make the ` setState ` method of ` PictureCanvas ` faster by
1037+ redrawing only the pixels that actually changed.
1038+
1039+ Remember that ` drawPicture ` is also used by the save button, so if you
1040+ change it, either make sure the changes don't break the old use, or
1041+ create a new version with a different name.
1042+
1043+ Also note that changing the size of a ` <canvas> ` element clears it,
1044+ making it entirely transparent again.
1045+
1046+ {{if interactive
1047+
1048+ ``` {test: no, lang: "text/html"}
1049+ <div></div>
1050+ <script>
1051+ // Change this method
1052+ PictureCanvas.prototype.setState = function(picture) {
1053+ if (this.picture == picture) return;
1054+ this.picture = picture;
1055+ drawPicture(this.picture, this.dom, scale);
1056+ }
1057+
1058+ // You may want to use or change this as well
1059+ function drawPicture(picture, canvas, scale) {
1060+ canvas.width = picture.width * scale;
1061+ canvas.height = picture.height * scale;
1062+ let cx = canvas.getContext("2d");
1063+
1064+ for (let y = 0; y < picture.height; y++) {
1065+ for (let x = 0; x < picture.width; x++) {
1066+ cx.fillStyle = picture.pixel(x, y);
1067+ cx.fillRect(x * scale, y * scale, scale, scale);
1068+ }
1069+ }
1070+ }
1071+
1072+ document.querySelector("div")
1073+ .appendChild(startPixelEditor({}));
1074+ </script>
1075+ ```
1076+
1077+ if}}
1078+
1079+ {{hint
1080+
1081+ This exercise is a good example of how immutable data structures can
1082+ make code _ faster_ . Because we have both the old and the new picture,
1083+ we can compare them and only redraw the pixels that changed color,
1084+ saving over 99% of the drawing work in most cases.
1085+
1086+ You can either write a new function ` updatePicture ` , or have
1087+ ` drawPicture ` take an extra argument, which may be either undefined or
1088+ a picture. For each pixel, it checks whether a previous picture was
1089+ passed, and if that previous picture has the same color at this
1090+ position, and skips the pixel when that is the case.
1091+
1092+ Because the canvas gets cleared when we change its size, you should
1093+ also avoid touching its ` width ` and ` height ` properties when the old
1094+ and the new picture have the same size. If they do not, you can set
1095+ the binding holding the old picture to null, because you shouldn't
1096+ skip any pixels after you've changed the canvas size.
1097+
1098+ hint}}
1099+
10281100### Circles
10291101
10301102Define a new tool called ` circle ` that draws a filled circle when you
0 commit comments