2.44

练习 2.44 请定义出corner-split里使用的过程up-split,它与right-split类似,除在其中交换了below和beside的角色之外。


暂未找到如何用 Scheme 操纵浏览器元素的简单方案,先用 JavaScript 来探索一下。这里重要的是通过高阶函数,使得在画面的操作被延迟到所有变换做完之后才执行。另外,为了能够方便地进行函数组合,需要将这些可以组合的函数的类型签名设计成一致。这里使用了 framedPainter,它的类型是 (x, y, w, h) -> ctx -> void。

由于所有的 framedPainter 都是这个类型,从而可以相互组合,并且组合完成后依然是 framedPainter,即 (x, y, w, h) -> ctx -> void。

Jeff Tian

the Jeff Tian painter

 
img = document.querySelector('#jeff-tian');
const framedPainter = (frameX, frameY, frameWidth, frameHeight) => (painter) => {
  painter(frameX, frameY, frameWidth, frameHeight);
};
drawToFrame = (frame, width, height) => (framedPainter) => {
  const canvas = document.querySelector(frame);
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  framedPainter(ctx);
};
const createPainter = (image, sourceX, sourceY, sourceWidth, sourceHeight) => (frameX, frameY, frameWidth, frameHeight) => (ctx) => {
  ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, frameX, frameY, frameWidth, frameHeight);
};
// (x, y, w, h) -> ctx -> void
jeffTianPainter = createPainter(img, 0, 0, img.naturalWidth, img.naturalHeight);
onLoadOrLoadAlready = (img, handler) => {
  if(img.complete) {
    handler();
  }
  img.addEventListener('load', handler);
}; 
onLoadOrLoadAlready(img, () => drawToFrame('#my-canvas', img.naturalWidth, img.naturalHeight)(jeffTianPainter(0, 0, img.naturalWidth, img.naturalHeight)));
img.onerror = () => {
  location.reload();
};
x
 
[Function anonymous]

beside

 
// ((x, y, w, h) -> ctx -> void, 
// (x, y, w, h) -> ctx -> void) ->
// (x, y, w, h) -> ctx -> void
beside = (painter1, painter2) => (x, y, w, h) => (ctx) => {
  ctx.save();
  painter1(x, y, Math.floor(w/2), h)(ctx);
  ctx.restore();
  ctx.save();
  painter2(x + Math.floor(w/2) + 1, y, Math.ceil(w/2), h)(ctx);
  ctx.restore();
};
// (x, y, w, h) -> ctx -> void
const besidePainter = beside(jeffTianPainter, jeffTianPainter);
onLoadOrLoadAlready(img, () => drawToFrame('#beside-canvas', img.naturalWidth, img.naturalHeight)(besidePainter(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

below

 
below = (painter1, painter2) => (x, y, w, h) => (ctx) => {
  ctx.save();
  painter2(x, y, w, Math.floor(h/2))(ctx);
  ctx.restore();
  ctx.save();
  painter1(x, y + Math.floor(h/2)+1, w, Math.ceil(h/2))(ctx);
  ctx.restore();
};
const belowPainter = below(jeffTianPainter, jeffTianPainter);
onLoadOrLoadAlready(img, () => drawToFrame('#below-canvas', img.naturalWidth, img.naturalHeight)(belowPainter(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

flip-vert

 
// painter -> ctx -> void
flipVert = (painter) => (x, y, w, h) => (ctx) => {
  ctx.save();
  ctx.scale(1, -1);
  painter(x, -y, w, -h)(ctx);
  ctx.restore();
}
const flipVertPainter = flipVert(jeffTianPainter);
onLoadOrLoadAlready(img, () => drawToFrame('#flip-vert-canvas', img.naturalWidth, img.naturalHeight)(flipVertPainter(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

flip-horiz

 
flipHoriz = (painter) => (x, y, w, h) => (ctx) => {
  ctx.save();
  ctx.scale(-1, 1);
  painter(-x, y, -w, h)(ctx);
  ctx.restore();
};
const flipHorizPainter = flipHoriz(jeffTianPainter);
onLoadOrLoadAlready(img, ()=>drawToFrame('#flip-horiz-canvas', img.naturalWidth, img.naturalHeight)(flipHorizPainter(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

wave2 with Jeff Tian Painter

 
wave2 = beside(jeffTianPainter, flipVert(jeffTianPainter));
onLoadOrLoadAlready(img, () => drawToFrame('#wave2-canvas', img.naturalWidth, img.naturalHeight)(wave2(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

wave4 with Jeff Tian Painter

 
const wave4 = below(wave2, wave2);
onLoadOrLoadAlready(img, () => drawToFrame('#wave4-canvas', img.naturalWidth, img.naturalHeight)(wave4(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

flipped-pairs with jeffTianPainter

 
const flippedPairs = (painter) => {
  const painter2 = beside(painter, flipVert(painter));
  return below(painter2, painter2);
}
const wave4 = flippedPairs(jeffTianPainter);
onLoadOrLoadAlready(img, () => drawToFrame('#flipped-pairs-canvas', img.naturalWidth, img.naturalHeight)(wave4(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

通过递归在图形的右边做分割和分支

 
/**
/------------------------\
|           |            |
|           |  rightSplit|
|           |     n-1    |
|  identity |------------|
|           |            |
|           |  rightSplit|
|           |     n-1    |
\-----------|------------/
*/
rightSplit = (painter, n) => {
  if (n === 0) {
    return painter;
  }
  const smaller = rightSplit(painter, n - 1);
  return beside(painter, below(smaller, smaller));
};
const rightSplit1 = rightSplit(jeffTianPainter, 1);
onLoadOrLoadAlready(img, () => drawToFrame('#right-split-1-canvas', img.naturalWidth, img.naturalHeight)(rightSplit1(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined
 
const rightSplit1 = rightSplit(jeffTianPainter, 4);
onLoadOrLoadAlready(img, () => drawToFrame('#right-split-4-canvas', img.naturalWidth, img.naturalHeight)(rightSplit1(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

向上分割

 
upSplit = (painter, n) => {
  if (n === 0) {
    return painter;
  }
  const smaller = upSplit(painter, n - 1);
  return below(beside(smaller, smaller), painter); 
};
const upSplit4 = upSplit(jeffTianPainter, 4);
onLoadOrLoadAlready(img, () => drawToFrame('#up-split-4-canvas', img.naturalWidth, img.naturalHeight)(upSplit4(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

通过同时在图形中向上和向右分支,产生出一种平衡模式

 
cornerSplit = (painter, n) => {
  if (n === 0) {
    return painter;
  }
  const up = upSplit(painter, n - 1);
  const right = rightSplit(painter, n - 1);
  const topLeft = beside(up, up);
  const bottomRight = below(right, right);
  const corner  = cornerSplit(painter, n - 1);
  return beside(below(topLeft, painter), below(corner, bottomRight));
}
const cornerSplit4 = cornerSplit(jeffTianPainter, 4);
onLoadOrLoadAlready(img, () => drawToFrame('#corner-split-4-canvas', img.naturalWidth, img.naturalHeight)(cornerSplit4(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

square-limit 模式

将某个 corner-split 的 4 个拷贝适当地组合起来,就得到了 square-limit 的模式:

 
const squareLimit = (painter, n) => {
  const quarter = cornerSplit(painter, n);
  const half = beside(flipHoriz(quarter), quarter);
  return below(flipVert(half), half);
};
const squareLimit4 = squareLimit(jeffTianPainter, 4);
onLoadOrLoadAlready(img, () => drawToFrame('#square-limit-4-canvas', img.naturalWidth, img.naturalHeight)(squareLimit4(0, 0, img.naturalWidth, img.naturalHeight)));
 
undefined

做到这里,失败了。这个 square-limit 的效果并不符合预期。通过仔细观察发现,我在前面粗暴实现的 flipHoriz,将嵌套中的每个小画家画的图像都进行了水平翻转,但是却没有将整个外面的大画家的画笔模式进行水平翻转,所以造成了这个事故。即虽然将 jeff Tian 的照片进行了水平翻转,但没有将 rightSplit 左右翻转成 leftSplit,造成最终效果不符合预期。

不过,做到这里,再回头看一下题目,只要求实现 up-split,这太简单了,前面为了画 corner-split 已经实现了一个并且成功,现在只需要翻译成 Scheme 就行了。

(define (up-split painter n)
    (if (= n 0)
        painter
        (let ((smaller (up-split painter (- n 1))))
            (below (beside smaller smaller) painter)
        )
    )
)

至于如何解决 square-limit 的问题,可以在后面的练习中逐步修正。问题的原因在于目前的 flipHoriz 和 flipVert 不能嵌套应用,后面应该会有针对性的练习,到时再解决这个问题。

在做完 练习2.52 后,这个问题解决了(通过像素版的 Jeff Tian Painter 完美解决)。

 
// placeholder, nothing here
 
undefined

results matching ""

    No results matching ""