绘制一个边长为15/20mm的矩形周长为2,将它绕其一边旋转一周,环绕此矩形周长为2,将它绕其一边旋转一周绘制一个边长为5/6mm的矩形周长为2,将它绕其一边旋转一周怎么画?

一、绘制矩形1.1 绘制矩形:Rectangle 函数
Rectangle 函数可以在窗口上绘制一个矩形,它的原型为:
BOOL Rectangle(
HDC hdc,
//设备环境句柄
int nLeftRect,
//矩形左上角x坐标
int nTopRect,
//矩形左上角y坐标
int nRightRect,
//矩形右下角x坐标
int nBottomRect
//矩形右下角y坐标
);
使用下面的代码 替换第一节中 WndProc函数`相应的部分
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Rectangle(hdc, 50, 50, 150, 150);
EndPaint(hwnd, &ps);
return 0 ;
2.1 绘制带圆角的矩形: RoundRect 函数
RoundRect函数可以画出带有圆角边框的矩形,原型为:
BOOL RoundRect(
HDC hdc,
//设备环境句柄
int nLeftRect,
//矩形左上角x坐标
int nTopRect,
//矩形左上角y坐标
int nRightRect,
//矩形右下角x坐标
int nBottomRect,
//矩形右下角y坐标
int nWidth,
//用来画圆角的椭圆的宽度
int nHeight
//用来画圆角的椭圆的高度
);
注意:当 nHeight >= nBottomRect 且 nWidth = nRightRect 时,那么绘制出的就是一个圆。
示例代码:case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
RoundRect(hdc, 20, 20, 150, 150, 25, 25);
EndPaint(hwnd, &ps);
return 0 ;
二、 绘制椭圆
Ellipse() 函数可以用来绘制椭圆(当 nRightRect - nLeftRect = nBottomRect - nRightRect 时绘制出的是一个圆),它的原型为:
BOOL Ellipse(
HDC hdc,
//设备环境句柄
int nLeftRect,
//左上角x坐标
int nTopRect,
//左上角y坐标
int nRightRect,
//右下角x坐标
int nBottomRect
//右下角y坐标
);
示例:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
Ellipse(hdc, 20, 20, 180,90);
EndPaint(hwnd, &ps);
return 0 ;
三、绘制直线
绘制直线需要确定起点和终点。确定起点使用 MoveToEx 函数。MoveToEx 用来指定画笔的起始位置,也就是从哪里开始画,它的原型为:
BOOL MoveToEx(
HDC hdc,
//设备环境句柄
int x,
//起始位置x坐标
int y,
//起始位置y坐标
LPPOINT lpPoint
//指向用于保存当前位置的POINT结构体的指针
);
对于参数 lpPoint,我们并不需要保存当前位置,所以直接指定为 NULL 即可。win32不再支持 MoveTo,只支持它的扩展函数 MoveToEx。有了起点,接下来就可以使用 LineTo 函数画直线了。LineTo 函数用于从当前绘图位置向指定点绘制一条直线,它的原型为:
BOOL LineTo(
HDC hdc,
//设备环境句柄
int xEnd,
//终点的x坐标
int yEnd
//终点的y坐标
);
示例:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
MoveToEx(hdc, 150, 150, NULL); //设定起始点,不保存当前点坐标
LineTo(hdc,200, 60); //第一条线
LineTo(hdc, 250, 150); //第二条线
LineTo(hdc, 150, 150); //第三条线
EndPaint(hwnd, &ps);
return 0 ;
}
前言本节我们通过简单的矩形绘制学习如何实现无限画布准备工作在绘制前,我们需要矫正 canvas 的分辨率,使用 appState 保存 canvas 相关的信息。新建一个 index.jsx 文件,初始化代码如下:const appState = {offsetLeft: 0,offsetTop: 0, }; const Canvas = memo(() => {const canvasRef = useRef(null);const canvasContainer = useRef(null);useEffect(() => {const canvas = canvasRef.current;const context = canvas.getContext("2d");const { offsetWidth, offsetHeight, offsetLeft, offsetTop } = canvas;canvas.width = offsetWidth * window.devicePixelRatio;canvas.height = offsetHeight * window.devicePixelRatio;context.scale(window.devicePixelRatio, window.devicePixelRatio);appState.offsetLeft = offsetLeft;appState.offsetTop = offsetTop;}, []);return (
绘制canvas
); }); 绘制坐标轴为方便观察,首先在 canvas 上绘制一个坐标轴。新建一个 renderScene.js 文件,实现 drawAxis 方法:const drawAxis = (ctx) => {ctx.save();const rectH = 100; // 纵轴刻度间距const rectW = 100; // 横轴刻度间距const tickLength = 8; // 刻度线长度const canvas = ctx.canvas;ctx.translate(0, 0);ctx.strokeStyle = "red";ctx.fillStyle = "red";// 绘制横轴和纵轴ctx.save();ctx.beginPath();ctx.setLineDash([10, 10]);ctx.moveTo(0, 0);ctx.lineTo(0, canvas.height);ctx.moveTo(0, 0);ctx.lineTo(canvas.width, 0);ctx.stroke();ctx.restore();// 绘制横轴和纵轴刻度ctx.beginPath();ctx.lineWidth = 2;ctx.textBaseline = "middle";for (let i = 0; i < canvas.height / rectH; i++) {// 绘制纵轴刻度ctx.moveTo(0, i * rectH);ctx.lineTo(tickLength, i * rectH);ctx.font = "20px Arial";ctx.fillText(i, -25, i * rectH);}for (let i = 1; i < canvas.width / rectW; i++) {// 绘制横轴刻度ctx.moveTo(i * rectW, 0);ctx.lineTo(i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(i, i * rectW - 5, -15);}ctx.stroke();ctx.restore(); }; const renderScene = (canvas) => {const context = canvas.getContext("2d");drawAxis(context); }; export default renderScene; 然后在 index.jsx 中引入 renderSceneuseEffect(() => {//...renderScene(canvas); }, []); 效果如下:绘制矩形屏幕坐标系转 canvas 坐标系在开始绘制矩形之前,我们先来看下屏幕坐标系如何转换成 canvas 坐标系。如下图所示,对于 canvas 上的任意一点,比如下面的 A 点。当我们点击事件位于 A 点时,我们可以获取到 A 点的屏幕坐标 (event.clientX, event.clientY)。那么 A 点的 canvas 坐标计算方式就是x = event.clientX - canvas.offsetLeft; y = event.clientY - canvas.offsetTop; 因此我们可以封装一个坐标系转换的工具方法viewportCoordsToSceneCoordsconst viewportCoordsToSceneCoords = ( { clientX, clientY },{ offsetLeft, offsetTop } ) => {const x = clientX - offsetLeft;const y = clientY - offsetTop;return { x, y }; }; 绘制矩形声明一个 elements 数组存放我们绘制的图形为 canvas 绑定一个onPointerDown={handleCanvasPointerDown}事件const handleCanvasPointerDown = (event) => {const origin = viewportCoordsToSceneCoords(event, appState);const pointerDownState = {origin,lastCoords: { ...origin },eventListeners: {onMove: null,onUp: null,},};const element = {x: pointerDownState.origin.x,y: pointerDownState.origin.y,width: 0,height: 0,strokeColor: "#000000",backgroundColor: "transparent",fillStyle: "hachure",strokeWidth: 1,strokeStyle: "solid",};appState.draggingElement = element;elements.push(element);const onPointerMove =onPointerMoveFromCanvasPointerDownHandler(pointerDownState);const onPointerUp = onPointerUpFromCanvasPointerDownHandler(pointerDownState);window.addEventListener("pointermove", onPointerMove);window.addEventListener("pointerup", onPointerUp);pointerDownState.eventListeners.onMove = onPointerMove;pointerDownState.eventListeners.onUp = onPointerUp; }; handleCanvasPointerDown事件主要做了以下几件事:调用viewportCoordsToSceneCoords方法将点击事件的屏幕坐标转换成 canvas 左标,并保存在 origin 中,这个也是我们绘制矩形的起点(即矩形的左上角的点)初始化一个 element 对象,这个 element 对象保存绘制矩形所需要的坐标信息以及颜色信息等将 element 对象添加到 elements 数组中,并保存在 appState.draggingElement 中,方便后续使用在 window 上注册pointermove和pointerup事件,其中pointermove事件用于计算鼠标移动的距离,计算矩形的宽度和高度。pointerup用于注销这两个事件,因为一旦鼠标离开,就说明绘制过程结束。onPointerUpFromCanvasPointerDownHandler实现如下:const onPointerUpFromCanvasPointerDownHandler =(pointerDownState) => (event) => {window.removeEventListener("pointermove",pointerDownState.eventListeners.onMove);window.removeEventListener("pointerup",pointerDownState.eventListeners.onUp);}; onPointerMoveFromCanvasPointerDownHandler事件逻辑如下:根据鼠标移动事件,计算当前点的 canvas 坐标计算矩形的宽高调用 renderScene 开始绘制const onPointerMoveFromCanvasPointerDownHandler =(pointerDownState) => (event) => {const pointerCoords = viewportCoordsToSceneCoords(event, appState);pointerDownState.lastCoords.x = pointerCoords.x;pointerDownState.lastCoords.y = pointerCoords.y;appState.draggingElement.width =pointerCoords.x - pointerDownState.origin.x;appState.draggingElement.height =pointerCoords.y - pointerDownState.origin.y;renderScene(canvasRef.current);}; renderScene新增 renderElements 方法const renderElements = (ctx) => {elements.forEach((ele) => {ctx.save();ctx.translate(ele.x, ele.y);ctx.strokeStyle = ele.strokeStyle;ctx.strokeColor = ele.strokeColor;ctx.strokeRect(0, 0, ele.width, ele.height);ctx.restore();}); }; const renderScene = (canvas) => {const context = canvas.getContext("2d");context.clearRect(0, 0, canvas.width, canvas.height);drawAxis(context);renderElements(context); }; 最终效果如下:现在,我们已经可以在画布上随意绘制矩形了。无限画布所谓无限画布,就是我们可以水平或者竖直方向滚动画布,并可以实现绘制。如下图,假设我们在 canvas 水平方向滚动了 scrollX,在竖直方向滚动了 scrollY 距离,那么我们原先的坐标系原点就从(0,0)的位置移动到了下图中的B点。对于滚动后的画布上面的任意一点,比如 A 点,A 点的坐标就变成了x = event.clientX - canvas.offsetLeft - scrollX; y = event.clientY - canvas.offsetTop - scrollY; 我们需要给 canvas 添加滚动事件onWheel={handleCanvasWheel},同时记录滚动距离。并重新绘制const handleCanvasWheel = (event) => {const { deltaX, deltaY } = event;appState.scrollX = appState.scrollX - deltaX;appState.scrollY = appState.scrollY - deltaY;renderScene(canvasRef.current, appState); }; 我们将滚动距离保存在appState中,并传入renderScene方法:const renderScene = (canvas, appState) => {const context = canvas.getContext("2d");context.clearRect(0, 0, canvas.width, canvas.height);drawAxis(context, appState);renderElements(context, appState); }; 由于坐标发生了改变,因此我们需要调整下 drawAxis 的逻辑。这里我绘制出了横轴和纵轴的正负刻度。const drawAxis = (ctx, { scrollX, scrollY }) => {ctx.save();const rectH = 100; // 纵轴刻度间距const rectW = 100; // 横轴刻度间距const tickLength = 8; // 刻度线长度const canvas = ctx.canvas;ctx.translate(scrollX, scrollY);ctx.strokeStyle = "red";ctx.fillStyle = "red";// 绘制横轴和纵轴ctx.save();ctx.beginPath();ctx.setLineDash([10, 10]);ctx.moveTo(0, -scrollY);ctx.lineTo(0, canvas.height - scrollY);ctx.moveTo(-scrollX, 0);ctx.lineTo(canvas.width - scrollX, 0);ctx.stroke();ctx.restore();// 绘制横轴和纵轴刻度ctx.beginPath();ctx.lineWidth = 2;ctx.textBaseline = "middle";for (let i = 0; i < scrollY / rectH; i++) {// 绘制纵轴负数刻度ctx.moveTo(0, -i * rectH);ctx.lineTo(tickLength, -i * rectH);ctx.font = "20px Arial";ctx.fillText(-i, -25, -i * rectH);}for (let i = 0; i < (canvas.height - scrollY) / rectH; i++) {// 绘制纵轴正数刻度ctx.moveTo(0, i * rectH);ctx.lineTo(tickLength, i * rectH);ctx.font = "20px Arial";ctx.fillText(i, -25, i * rectH);}for (let i = 1; i < scrollX / rectW; i++) {// 绘制横轴负数刻度ctx.moveTo(-i * rectW, 0);ctx.lineTo(-i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(-i, -i * rectW - 10, -15);}for (let i = 1; i < (canvas.width - scrollX) / rectW; i++) {// 绘制横轴正数刻度ctx.moveTo(i * rectW, 0);ctx.lineTo(i * rectW, tickLength);ctx.font = "20px Arial";ctx.fillText(i, i * rectW - 5, -15);}ctx.stroke();ctx.restore(); }; 坐标轴效果如下:可以看出坐标轴的绘制和滚动距离完全对应的上。我们已经能够实现一个无限画布并且正确绘制坐标轴,但此时如果我们在上面绘制一个矩形就会发现,矩形的宽度和高度是正确的,同时矩形的原点,即 x,y 也是正确的,但是矩形绘制的位置并不对。这是因为我们这里矩形的位置是相对于移动后的坐标系。因此我们需要修改我们的 renderElement 方法const renderElements = (ctx, appState) => {elements.forEach((ele) => {ctx.save();ctx.translate(ele.x + appState.scrollX, ele.y + appState.scrollY);ctx.strokeStyle = ele.strokeStyle;ctx.strokeColor = ele.strokeColor;ctx.strokeRect(0, 0, ele.width, ele.height);ctx.restore();}); }; 修改后,我们就可以正常绘制矩形了至此,我们就可以实现 canvas 无限画布,并能够在上面绘制矩形。导出现在,我们希望能够将我们的画布导出成 png 图片。很快,我们就可以实现下面的代码: {const canvas = canvasRef.current;var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG 但是我们导出的时候会发现只能导出视图内的图形,视图以外的图形(即画面中看不到的图形)无法导出,这显然不符合我们的需求我们需要计算elements中最小的 minX 和 minY,以及最大的 maxX 和 maxY,并重新创建一个画布绘制,然后在这个新的画布上绘制我们的图形,修改导出代码 {let minX = Infinity;let maxX = -Infinity;let minY = Infinity;let maxY = -Infinity;elements.forEach((element) => {const [x1, y1, x2, y2] = [element.x,element.y,element.x + element.width,element.y + element.height,];minX = Math.min(minX, x1);minY = Math.min(minY, y1);maxX = Math.max(maxX, x2);maxY = Math.max(maxY, y2);});const canvas = document.createElement("canvas");canvas.width = (maxX - minX + 20) * window.devicePixelRatio;canvas.height = (maxY - minY + 20) * window.devicePixelRatio;const context = canvas.getContext("2d");context.scale(window.devicePixelRatio, window.devicePixelRatio);renderScene(canvas, {...appState,scrollX: -minX + 10,scrollY: -minY + 10,});console.log("导出", elements);var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG -minX + 10,scrollY: -minY + 10,});console.log("导出", elements);var a = document.createElement("a");a.href = canvas.toDataURL();a.download = "canvas.png";a.click();}} >导出PNG 可以看到,现在一切正常最后最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。 有需要的小伙伴,可以点击下方卡片领取,无偿分享 }

我要回帖

更多关于 矩形周长为2,将它绕其一边旋转一周 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信