2.52

练习 2.52 在上面描述的各个层次上工作,修改图2-9中所示的方块的限制。特别是:

a)给练习2.49的基本wave画家加入某些线段(例如,加上一个笑脸)。

b)修改 corner-split 的构造模式(例如,只用up-split和right-split的图像的各一个副本,而不是两个)。

c)修改square-limit,换一种使用square-of-four的方式,以另一种不同模式组合起各个角区(例如,你可以让大的Rogers先生从正方形的每个角向外看)。


为了方便直观地体验,将采用 js 实现。

a)

先复用一下上一练习的代码,然后增加了两条眼线的线段,下滑看效果,有点兔斯基的感觉。

cons = (x, y) => [x, y];
car = (z) => z[0];
cdr = (z) => z[1];

makeVect = cons;
xCorVect = car;
yCorVect = cdr;

makeSegment = cons;
startSegment = car;
endSegment = cdr;

o = makeVect(0, 0);
e1 = makeVect(300, 0);
e2 = makeVect(0, 150); 

s = makeSegment(makeVect(1, 0), makeVect(0, 1)); 
console.log('s = ', s);

console.log(startSegment(s));
console.log(endSegment(s));
makeFrame = (origin, edge1, edge2) => {
    return [origin, edge1, edge2];
};

originFrame = (frame) => frame[0];
edge1Frame = (frame) => frame[1];
edge2Frame = (frame) => frame[2];

addVect = (v1, v2) => makeVect(xCorVect(v1) + xCorVect(v2), yCorVect(v1) + yCorVect(v2));
subVect = (v1, v2) => makeVect(xCorVect(v1) - xCorVect(v2), yCorVect(v1) - yCorVect(v2));
scaleVect = (s, v) => makeVect(s * xCorVect(v), s * yCorVect(v));

frameCoordMap = (frame) => {
    return (v) => {
        return addVect(
            originFrame(frame),
            addVect(
                scaleVect(
                    xCorVect(v),
                    edge1Frame(frame)
                ),
                scaleVect(
                    yCorVect(v),
                    edge2Frame(frame)
                )
            )
        );
    };
}

f = makeFrame(o, e1, e2);
drawLine = (v1, v2) => {
    return (ctx) => {
        ctx.beginPath();
        ctx.moveTo(xCorVect(v1), yCorVect(v1));
        ctx.lineTo(xCorVect(v2), yCorVect(v2));
        ctx.stroke();
    }
};

segmentsPainter = (segmentList) => {
    return (frame) => (ctx) => {
        const m = frameCoordMap(frame);

        segmentList.forEach((segment) => {
            drawLine(
                m(startSegment(segment)),
                m(endSegment(segment))
            )(ctx);
        });
    }
}

applyToCanvas = (painter, canvasSelector, setWH = false) => {
    const canvas = document.querySelector(canvasSelector);

    if(setWH) {
        const originalRatio = canvas.width/canvas.height;
        canvas.width = 1000;
        canvas.height = canvas.width / originalRatio;
    }

    const ctx = canvas.getContext('2d');

    ctx.clearRect(0, 0, canvas.width, canvas.height); 

    painter(ctx);
}
wavePainter = (frame) => {
    const v1 = makeVect(0.4, 0);
    const v2 = makeVect(0.6, 0);
    const v3 = makeVect(0, 0.2);
    const v4 = makeVect(0.3, 0.2);
    const v5 = makeVect(0.7, 0.2);
    const v6 = makeVect(0, 0.4);
    const v7 = makeVect(0.2, 0.4);
    const v8 = makeVect(0.3, 0.35);
    const v9 = makeVect(0.42, 0.4);
    const v10 = makeVect(0.6, 0.4);
    const v11 = makeVect(0.8, 0.4);
    const v12 = makeVect(0.3, 0.4);
    const v13 = makeVect(0.2, 0.55);
    const v14 = makeVect(0.31, 0.5);
    const v15 = makeVect(0.6, 0.51);
    const v16 = makeVect(1, 0.7);
    const v17 = makeVect(0.5, 0.8);
    const v18 = makeVect(1, 0.85);
    const v19 = makeVect(0.35, 1);
    const v20 = makeVect(0.4, 1);
    const v21 = makeVect(0.6, 1);
    const v22 = makeVect(0.7, 1);

    const v23 = makeVect(0.5, 0.1);
    const v24 = makeVect(0.55, 0.1);

    const v25 = makeVect(0.6, 0.1);
    const v26 = makeVect(0.65, 0.1);

    return segmentsPainter([
        makeSegment(v1, v4),
        makeSegment(v2, v5),
        makeSegment(v3, v7), 
        makeSegment(v7, v8),
        makeSegment(v8, v9),
        makeSegment(v9, v4),
        makeSegment(v10, v5),
        makeSegment(v10, v11),
        makeSegment(v6, v13),
        makeSegment(v13, v12),
        makeSegment(v12, v14),
        makeSegment(v16, v11),
        makeSegment(v18, v15),
        makeSegment(v19, v14),
        makeSegment(v20, v17),
        makeSegment(v21, v17),
        makeSegment(v22, v15),

        makeSegment(v23, v24),
        makeSegment(v25, v26),
    ])(frame); 
}

applyToCanvas(wavePainter(f), '#wave-canvas');
transformPainter = (painter, origin, corner1, corner2) => {
    return (frame) => {
        const m = frameCoordMap(frame);
        const newOrigin = m(origin);
        return painter(
            makeFrame(
                newOrigin, 
                subVect(m(corner1), newOrigin),
                subVect(m(corner2), newOrigin)
            )
        );
    }
}

flipHoriz = (painter) => {
    return transformPainter(
        painter,
        makeVect(1, 0),
        makeVect(0, 0),
        makeVect(1, 1)
    )
}
rotateLeft180 = (painter) => {
    return transformPainter(
        painter,
        makeVect(1, 1),
        makeVect(0, 1),
        makeVect(1, 0)
    )
}
rotateLeft270 = (painter) => {
    return transformPainter(
        painter,
        makeVect(1, 0),
        makeVect(1, 1),
        makeVect(0, 0)
    )
}

b

可以和 练习 2.44 对比着看,这里只用 rightSplit 完成同样的效果。

below = (painter1, painter2) => {
  const splitPoint = makeVect(0, 0.5);
  const paintBelow = transformPainter(
    painter1,
    splitPoint,
    makeVect(1, 0.5),
    makeVect(0, 1)
  );
  const paintUp = transformPainter(
    painter2,
    makeVect(0, 0),
    makeVect(1, 0),
    splitPoint
  );

  return frame => ctx => {
    paintBelow(frame)(ctx);
    paintUp(frame)(ctx);
  }
}

beside = (painter1, painter2) => {
    const splitPoint = makeVect(0.5, 0);
    const paintLeft = transformPainter(
        painter1, 
        makeVect(0, 0),
        splitPoint,
        makeVect(0, 1)
    );
    const paintRight = transformPainter(
        painter2,
        splitPoint,
        makeVect(1, 0),
        makeVect(0.5, 1)
    );

    return frame => ctx => {
        paintLeft(frame)(ctx);
        paintRight(frame)(ctx);
    };
};

rightSplit = (painter, n) => {
    if (n === 0) {
        return painter;
    }

    const smaller = rightSplit(painter, n-1);
    return beside(painter, below(smaller, smaller));
};

rotateLeft90 = (painter) => {
  return transformPainter(
    painter,
    makeVect(0, 1),
    makeVect(0, 0),
    makeVect(1, 1)
  )
}

cornerSplit = (painter, n) => {
    if(n === 0) {
        return painter;
    }

    const right = rightSplit(painter, n-1);
    const up = rotateLeft90(
        rightSplit(
            rotateLeft90(
                rotateLeft90(
                    rotateLeft90(
                        painter
                    )
                )
            ), 
            n-1
        )
    );

    const topLeft = beside(up, up);
    const bottomRight = below(right, right);
    const corner = cornerSplit(painter, n-1);

    return beside(
            below(painter, topLeft), 
            below(bottomRight, corner)
    );
}

applyToCanvas(cornerSplit(wavePainter, 4)(f), '#corner-split-canvas');

c

squareOfFour = (tl, tr, bl, br) => {
    return (painter) => {
        const top = beside(tl(painter), tr(painter));
        const bottom = beside(bl(painter), br(painter));

        return below(bottom, top);
    };
};

id = x => x;
flipVert = (painter) => {
    return transformPainter(
        painter,
        makeVect(0, 1),
        makeVect(1, 1),
        makeVect(0, 0)
    );
};

squareLimit = (painter, n) => {
    const combine4 = squareOfFour(flipHoriz, id, rotateLeft180, flipVert);

    return combine4(cornerSplit(painter, n));
};

applyToCanvas(squareLimit(wavePainter, 4)(f), '#square-of-four-canvas');

至此,可以解决 练习 2.44 最后的一个问题了,只需要替换 wavePainter 为 jeffTianPainter 即可。

Jeff Tian

jeffTianPainter = (frame) => ctx => {
    const jeffTian = document.querySelector('#jeff-tian');

    const m = frameCoordMap(frame);
    const v1 = makeVect(0, 0);
    const v2 = makeVect(1, 1);

    const x = xCorVect(m(v1));
    const y = yCorVect(m(v1));
    const w = xCorVect(m(v2)) - x;
    const h = yCorVect(m(v2)) - y;

    ctx.drawImage(jeffTian, 0, 0, jeffTian.naturalWidth, jeffTian.naturalHeight, x, y, w, h);
};

applyToCanvas(squareLimit(jeffTianPainter, 4)(f), '#solved', false);

但是这样看起来,仍然不是预期的。虽然解决了子画布没有旋转的问题,但是带了新的问题,即翻转操作无效。现在知道了,2.44 中的问题在于使用了 ctx.scale(),导致子画布的旋转被撤销了。而现在的问题,是由于没有使用 ctx.scale(),所以直接 drawImage,实现不了水平或者垂直方向上的旋转。

segmentsPainter 没有这个问题,因为是矢量形式。要彻底解决画图片的问题,必须对 canvas 做深度介入,或者逐像素读取图片,一个点一个点的去画(将图片上每个点在画布上的位置都做一个计算)。

像素点画家:

pixelPainter = (img) => (frame) => (ctx) => {
    const m = frameCoordMap(frame);

    const o = originFrame(frame);
    const e1 = edge1Frame(frame);
    const e2 = edge2Frame(frame);

    const frameWidth = Math.round(Math.sqrt(
                Math.pow(xCorVect((e1)) - xCorVect(o), 2) +
                Math.pow(yCorVect((e1)) - yCorVect(o), 2)
    ));
    const frameHeight = Math.round(Math.sqrt(
        Math.pow(xCorVect((e2)) - xCorVect(o), 2) +
        Math.pow(yCorVect((e2)) - yCorVect(o), 2)
    ));

    const wStep = 1/frameWidth;
    const hStep = 1/frameHeight;

    for (let y = 0; y <= 1; y += hStep) {
        for (let x = 0; x <= 1; x += wStep) {
            const logicalPixel = makeVect(x, y);
            const transformedPixel = m(logicalPixel);

            const frameX = xCorVect(transformedPixel);
            const frameY = yCorVect(transformedPixel);

            const imageX = x * img.naturalWidth;
            const imageY = y * img.naturalHeight;

            ctx.drawImage(img, 
                imageX, imageY, 1, 1, 
                frameX, frameY, 1, 1);
        }
    }
};

applyToCanvas(pixelPainter(document.getElementById('jeff-tian'))(f), '#pixel-canvas', false);

对像素点画家应用 squareLimit 效果

至此,就修复了2.44中的 squareLimit 在 Jeff Tian Painter 上的应用!

但是有两个小问题:

1、性能不够好;

2、清晰度不高。


const jeffTianPixel = pixelPainter(document.getElementById('jeff-tian'));

applyToCanvas(squareLimit(jeffTianPixel, 2)(f), '#square-limit-to-pixel', false);

现在,要实现向外看的效果,可以简单变形一下,即将最初的 wavePainter 水平翻转,做为最底层的画家:

const squareLimit = (painter, n) => {
    const combine4 = squareOfFour(flipHoriz, id, rotateLeft180, flipVert);

    return combine4(cornerSplit(painter, n));
};

applyToCanvas(squareLimit(flipHoriz(wavePainter), 4)(f), '#square-of-four-2-canvas');
console.log('done');

results matching ""

    No results matching ""