Canvas 封装

Canvas 封装

记录一个Canvas封装,这个是给小程序用的,html可以直接用html2canvas,且不说小程序有没有对应的东西,小程序这原生的东西对于第三方框架都不一定有用,毕竟小程序就是一坨,用不了带document的东西,啥新特性都没有,还一堆bug不修

这里使用的是Taro vue3 typescript,用别的框架把Taro相关的东西改掉就行
直接搞一个工具类出来

export class Canvas2DHelper
{

}

创建离屏Canvas

离屏Canvas在小程序里面跟正常的Canvas的api基本一致,但是在html里面区别蛮大的,我这里是给小程序用的,自己注意

/**
 * 创建离屏canvas
 * @param width 宽
 * @param height 高
 * @returns 
 */
public static CreateOffscreenCanvas(width: number = 0, height: number = 0): Taro.OffscreenCanvas
{
    let canvas: Taro.OffscreenCanvas = Taro.createOffscreenCanvas({
        type: "2d",
    });

    return canvas;
}

保存和读取

基本操作,首先要保存初始化之后的状态,之后每次操作结束后先读取再保存

/**
 * 保存canvas
 * @param canvas 
 */
public static SaveCanvas(canvas: Canvas): void
{
    let canvasContext = canvas.getContext("2d");
    //保存状态
    canvasContext.save();
}

/**
 * 恢复canvas
 * @param canvas 
 */
public static ReloadCanvas(canvas: Canvas): void
{
    let canvasContext = canvas.getContext("2d");
    //恢复状态
    canvasContext.restore();
}

设置像素比

我觉得这个算是比较没用的,似乎并不能让像素对上,更糊了,而且之后的操作都要除以2,怪麻烦的,不如嗯写就完事了

/**
 * 设置canvas像素
 * @param canvas 
 * @param devicePixelRatio 设备像素比
 * @param deviceWidth 设备宽
 * @param deviceHeight 设备高
 */
public static SetPixelRatio(canvas: Canvas, devicePixelRatio: number, deviceWidth: number, deviceHeight: number): void
{
    let canvasContext = canvas.getContext("2d");
    canvas.height = deviceHeight * devicePixelRatio;
    canvas.width = deviceWidth * devicePixelRatio;
    canvasContext.scale(devicePixelRatio, devicePixelRatio);
}

裁剪

/**
 * 裁剪canvas
 * @param canvas 
 */
public static ClipCanvas(canvas: Canvas): void
{
    let canvasContext = canvas.getContext("2d");

    //裁剪canvas
    canvasContext.clip();
}

绘制矩形

绘制矩形还是很简单的,不过为了规范和方便,先整一个Option类出来
我这里单纯绘制图形是没有颜色的,只是闭合路径,方便后续操作

export class CreateRectangleOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }


    private _width: number = 0;
    /**
     * 宽度
     */
    public get Width(): number
    {
        return this._width;
    }
    public set Width(v: number)
    {
        this._width = v;
    }


    private _height: number = 0;
    /**
     * 高度
     */
    public get Height(): number
    {
        return this._height;
    }
    public set Height(v: number)
    {
        this._height = v;
    }

    private _x: number = 0;
    /**
     * 起始坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }


    private _y: number = 0;
    /**
     * 起始坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }


    constructor()
    {
        this.Canvas = null;
        this.Width = 0;
        this.Height = 0;
        this.X = 0;
        this.Y = 0;
    }
}

再把这个类作为参数传进来

/**
 * 绘制矩形
 * @param option 
 */
public static CreateRectangle(option: CreateRectangleOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;

    let canvasContext = canvas.getContext("2d");

    //选择背景色
    canvasContext.fillStyle = backgroundColor;
    //绘制矩形
    canvasContext.fillRect(x, y, width, height);
}

绘制矩形并描边

这里再来一个配置类,其实大部分属性跟矩形的配置是一样的,可以用继承

export class CreateRectangleStrokeColorOptionModel extends CreateRectangleOptionModel
{
    private _strokeColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get StrokeColor(): string
    {
        return this._strokeColor;
    }
    public set StrokeColor(v: string)
    {
        this._strokeColor = v;
    }


    private _lineWidth: number = 1;
    /**
     * 描边宽度
     */
    public get LineWidth(): number
    {
        return this._lineWidth;
    }
    public set LineWidth(v: number)
    {
        this._lineWidth = v;
    }


    constructor()
    {
        super();
        this.StrokeColor = "#000000";
        this.LineWidth = 1;
    }
}

对应的函数

/**
 * 绘制矩形,描边
 * @param option 
 */
public static CreateRectangleStrokeColor(option: CreateRectangleStrokeColorOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;
    let color = option.StrokeColor;
    let lineWidth = option.LineWidth;

    let createRectangleOptionModel = new CreateRectangleOptionModel();
    createRectangleOptionModel.Canvas = canvas;
    createRectangleOptionModel.Width = width;
    createRectangleOptionModel.Height = height;
    createRectangleOptionModel.X = x;
    createRectangleOptionModel.Y = y;

    //绘制矩形
    Canvas2DHelper.CreateRectangle(createRectangleOptionModel);

    let canvasContext = canvas.getContext("2d");

    //描边宽度
    canvasContext.lineWidth = lineWidth;
    //选择描边颜色
    canvasContext.strokeStyle = color;
    //描边
    canvasContext.stroke();
}

绘制矩形并填充颜色

export class CreateRectangleFillBackgroundColorOptionModel extends CreateCircleOptionModel
{
    private _backgroundColor: string;
    /**
     * 背景颜色
     */
    public get BackgroundColor(): string
    {
        return this._backgroundColor;
    }
    public set BackgroundColor(v: string)
    {
        this._backgroundColor = v;
    }


    constructor()
    {
        super();
        this.BackgroundColor = "#000000";
    }
}
/**
 * 绘制矩形填充背景色
 * @param option 
 */
public static CreateRectangleBackgroundColor(option: CreateRectangleFillBackgroundColorOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;
    let backgroundColor = option.BackgroundColor;

    let canvasContext = canvas.getContext("2d");

    //选择背景色
    canvasContext.fillStyle = backgroundColor;
    //绘制矩形
    canvasContext.fillRect(x, y, width, height);
    //填充背景色
    canvasContext.fill();
}

绘制圆形

export class CreateCircleOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }

    private _x: number = 0;
    /**
     * 圆心坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }

    private _y: number = 0;
    /**
     * 圆心坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }

    private _radius: number = 0;
    /**
     * 半径
     */
    public get Radius(): number
    {
        return this._radius;
    }
    public set Radius(v: number)
    {
        this._radius = v;
    }


    constructor()
    {
        this.Canvas = null;
        this.X = 0;
        this.Y = 0;
        this.Radius = 0;
    }
}
/**
 * 绘制圆形
 * @param option 
 */
public static CreateCircle(option: CreateCircleOptionModel): void
{
    let canvas = option.Canvas;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;

    let canvasContext = canvas.getContext("2d");

    //开始路径
    canvasContext.beginPath();

    //绘制圆形
    canvasContext.arc(x, y, radius, 0, 2 * Math.PI);

    //关闭路径
    canvasContext.closePath();
}

绘制圆形并描边

export class CreateCircleStrokeColorOptionModel extends CreateCircleOptionModel
{

    private _strokeColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get StrokeColor(): string
    {
        return this._strokeColor;
    }
    public set StrokeColor(v: string)
    {
        this._strokeColor = v;
    }

    private _lineWidth: number = 1;
    /**
     * 描边宽度
     */
    public get LineWidth(): number
    {
        return this._lineWidth;
    }
    public set LineWidth(v: number)
    {
        this._lineWidth = v;
    }


    constructor()
    {
        super();
        this.StrokeColor = "#000000";
        this.LineWidth = 1;
    }
}
/**
 * 绘制圆形,描边
 * @param option 
 */
public static CreateCircleStrokeColor(option: CreateCircleStrokeColorOptionModel): void
{
    let canvas = option.Canvas;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;
    let color = option.StrokeColor;
    let lineWidth = option.LineWidth;

    let createCircleOptionModel = new CreateCircleOptionModel();
    createCircleOptionModel.Canvas = canvas;
    createCircleOptionModel.X = x;
    createCircleOptionModel.Y = y;
    createCircleOptionModel.Radius = radius;

    //绘制圆形
    Canvas2DHelper.CreateCircle(createCircleOptionModel);

    let canvasContext = canvas.getContext("2d");

    //描边宽度
    canvasContext.lineWidth = lineWidth;
    //选择描边颜色
    canvasContext.strokeStyle = color;
    //描边
    canvasContext.stroke();
}

绘制圆形并填充颜色

export class CreateCircleFillBackgroundColorOptionModel extends CreateCircleOptionModel
{
    private _backgroundColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get BackgroundColor(): string
    {
        return this._backgroundColor;
    }
    public set BackgroundColor(v: string)
    {
        this._backgroundColor = v;
    }

    constructor()
    {
        super();
        this.BackgroundColor = "#000000";
    }
}
/**
 * 绘制圆形,填充颜色
 * @param option 
 */
public static CreateCircleFillBackgroundColor(option: CreateCircleFillBackgroundColorOptionModel)
{
    let canvas = option.Canvas;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;
    let backgroundColor = option.BackgroundColor;

    let createCircleOptionModel = new CreateCircleOptionModel();
    createCircleOptionModel.Canvas = canvas;
    createCircleOptionModel.X = x;
    createCircleOptionModel.Y = y;
    createCircleOptionModel.Radius = radius;

    //绘制圆形
    Canvas2DHelper.CreateCircle(createCircleOptionModel);

    let canvasContext = canvas.getContext("2d");

    //选择背景色
    canvasContext.fillStyle = backgroundColor;
    //填充背景色
    canvasContext.fill();
}

绘制圆角矩形

export class CreateRoundRectangleOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }


    private _width: number = 0;
    /**
     * 宽度
     */
    public get Width(): number
    {
        return this._width;
    }
    public set Width(v: number)
    {
        this._width = v;
    }


    private _height: number = 0;
    /**
     * 高度
     */
    public get Height(): number
    {
        return this._height;
    }
    public set Height(v: number)
    {
        this._height = v;
    }

    private _x: number = 0;
    /**
     * 起始坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }


    private _y: number = 0;
    /**
     * 起始坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }

    private _radius: number = 0;
    /**
     * 圆角半径
     */
    public get Radius(): number
    {
        return this._radius;
    }
    public set Radius(v: number)
    {
        this._radius = v;
    }

    constructor()
    {
        this.Canvas = null;
        this.Width = 0;
        this.Height = 0;
        this.X = 0;
        this.Y = 0;
        this.Radius = 0;
    }

}
/**
 * 绘制圆角矩形
 * @param option 
 */
public static CreateRoundRectangle(option: CreateRoundRectangleOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;

    let canvasContext = canvas.getContext("2d");

    //开始路径
    canvasContext.beginPath();
    //左上角的右上点
    canvasContext.moveTo(x + radius, y);
    //右上角圆角
    canvasContext.arcTo(x + width, y, x + width, y + height, radius);
    //右下角圆角
    canvasContext.arcTo(x + width, y + height, x, y + height, radius);
    //左下角圆角
    canvasContext.arcTo(x, y + height, x, y, radius);
    //左上角圆角
    canvasContext.arcTo(x, y, x + width, y, radius);
    //合并路径
    canvasContext.closePath();
}

绘制圆角矩形并描边

export class CreateRoundRectangleStrokeColorOptionModel extends CreateRoundRectangleOptionModel
{
    private _strokeColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get StrokeColor(): string
    {
        return this._strokeColor;
    }
    public set StrokeColor(v: string)
    {
        this._strokeColor = v;
    }

    private _lineWidth: number = 1;
    /**
     * 描边宽度
     */
    public get LineWidth(): number
    {
        return this._lineWidth;
    }
    public set LineWidth(v: number)
    {
        this._lineWidth = v;
    }

    constructor()
    {
        super();
        this.StrokeColor = "#000000";
        this.LineWidth = 1;
    }

}
/**
 * 绘制圆角矩形,描边
 * @param option 
 */
public static CreateRoundRectangleStrokeColor(option: CreateRoundRectangleStrokeColorOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;
    let color = option.StrokeColor;
    let lineWidth = option.LineWidth;

    let createRoundRectangleOptionModel = new CreateRoundRectangleOptionModel();
    createRoundRectangleOptionModel.Canvas = canvas;
    createRoundRectangleOptionModel.Width = width;
    createRoundRectangleOptionModel.Height = height;
    createRoundRectangleOptionModel.X = x;
    createRoundRectangleOptionModel.Y = y;
    createRoundRectangleOptionModel.Radius = radius;

    //绘制矩形
    Canvas2DHelper.CreateRoundRectangle(createRoundRectangleOptionModel);

    let canvasContext = canvas.getContext("2d");

    //描边宽度
    canvasContext.lineWidth = lineWidth;
    //选择描边颜色
    canvasContext.strokeStyle = color;
    //描边
    canvasContext.stroke();
}

绘制圆角矩形并填充颜色

export class CreateRoundRectangleFillBackgroundColorOptionModel extends CreateRoundRectangleOptionModel
{
    private _backgroundColor: string;
    /**
     * 背景颜色
     */
    public get BackgroundColor(): string
    {
        return this._backgroundColor;
    }
    public set BackgroundColor(v: string)
    {
        this._backgroundColor = v;
    }


    constructor()
    {
        super();
        this.BackgroundColor = "#000000";
    }

}
/**
 * 绘制圆角矩形,填充颜色
 * @param option 
 */
public static CreateRoundRectangleFillBackgroundColor(option: CreateRoundRectangleFillBackgroundColorOptionModel): void
{
    let canvas = option.Canvas;
    let width = option.Width;
    let height = option.Height;
    let x = option.X;
    let y = option.Y;
    let radius = option.Radius;
    let backgroundColor = option.BackgroundColor;

    let createRoundRectangleOptionModel = new CreateRoundRectangleOptionModel();
    createRoundRectangleOptionModel.Canvas = canvas;
    createRoundRectangleOptionModel.Width = width;
    createRoundRectangleOptionModel.Height = height;
    createRoundRectangleOptionModel.X = x;
    createRoundRectangleOptionModel.Y = y;
    createRoundRectangleOptionModel.Radius = radius;

    //绘制矩形
    Canvas2DHelper.CreateRoundRectangle(createRoundRectangleOptionModel);

    let canvasContext = canvas.getContext("2d");

    //选择背景色
    canvasContext.fillStyle = backgroundColor;
    //填充背景色
    canvasContext.fill();
}

绘制文字

绘制文字是比较麻烦的,因为有换行和省略号的问题
我这里是没有配置字体的,要配置字体就自己稍微改改

export class CreateTextOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }


    private _text: string = "";
    /**
     * 文本
     */
    public get Text(): string
    {
        return this._text;
    }
    public set Text(v: string)
    {
        this._text = v;
    }


    private _fontSize: number = 14;
    /**
     * 字体大小
     */
    public get FontSize(): number
    {
        return this._fontSize;
    }
    public set FontSize(v: number)
    {
        this._fontSize = v;
    }


    private _fontWeight: number = 500;
    /**
     * 字体粗细
     */
    public get FontWeight(): number
    {
        return this._fontWeight;
    }
    public set FontWeight(v: number)
    {
        this._fontWeight = v;
    }

    private _fontColor: string = "#000000";
    /**
     * 字体颜色
     */
    public get FontColor(): string
    {
        return this._fontColor;
    }
    public set FontColor(v: string)
    {
        this._fontColor = v;
    }


    private _isLineBreak: boolean = false;
    /**
     * 是否换行
     */
    public get IsLineBreak(): boolean
    {
        return this._isLineBreak;
    }
    public set IsLineBreak(v: boolean)
    {
        this._isLineBreak = v;
    }


    private _isOVerflowEllipsis: boolean = false;
    /**
     * 超出是否用省略号
     */
    public get IsOverflowEllipsis(): boolean
    {
        return this._isOVerflowEllipsis;
    }
    public set IsOverflowEllipsis(v: boolean)
    {
        this._isOVerflowEllipsis = v;
    }


    private _lineCount: number = 1;
    /**
     * 行数
     */
    public get LineCount(): number
    {
        return this._lineCount;
    }
    public set LineCount(v: number)
    {
        this._lineCount = v;
    }


    private _lineWidth: number = 100;
    /**
     * 行宽
     */
    public get LineWidth(): number
    {
        return this._lineWidth;
    }
    public set LineWidth(v: number)
    {
        this._lineWidth = v;
    }

    private _lineHeight: number = 21;
    /**
     * 行高
     */
    public get LineHeight(): number
    {
        return this._lineHeight;
    }
    public set LineHeight(v: number)
    {
        this._lineHeight = v;
    }

    private _x: number = 0;
    /**
     * 基线坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }


    private _y: number = 0;
    /**
     * 基线坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }

    constructor()
    {
        this.Canvas = null;
        this.Text = "";
        this.FontSize = 14;
        this.FontWeight = 500;
        this.FontColor = "#000000";
        this.IsLineBreak = false;
        this.IsOverflowEllipsis = false;
        this.LineCount = 1;
        this.LineWidth = 100;
        this.LineHeight = 21;
        this.X = 0;
        this.Y = 0;
    }
}
/**
 * 绘制文字
 * @param option 
 * @returns 返回最大行宽
 */
public static CreateText(option: CreateTextOptionModel): number
{
    let maxLineWidth = 0;
    let lineWidthList: Array<number> = [];
    let textList: Array<string> = [];

    let canvas = option.Canvas;
    let text = option.Text;
    let fontSize = option.FontSize;
    let fontWeight = option.FontWeight;
    let fontColor = option.FontColor;
    let isLineBreak = option.IsLineBreak;
    let isOverflowEllipsis = option.IsOverflowEllipsis;
    let lineCount = option.LineCount;
    let lineWidth = option.LineWidth;
    let lineHeight = option.LineHeight;
    let x = option.X;
    let y = option.Y;

    let canvasContext = canvas.getContext("2d");

    //选择字体颜色
    canvasContext.fillStyle = fontColor;

    //字体大小
    canvasContext.font = `${fontWeight} ${fontSize}px sans-serif`;

    if (true == isLineBreak)
    {
        //换行
        if (true == isOverflowEllipsis)
        {
            //有省略号
            textList = Canvas2DHelper.GetLineBreakWithOverflowEllipsisTextList(canvas, text, fontSize, fontWeight, lineWidth, lineCount);
        }
        else
        {
            //无省略号
            textList = Canvas2DHelper.GetLineBreakTextList(canvas, text, fontSize, fontWeight, lineWidth);
        }
    }
    else
    {
        //不换行
        if (true == isOverflowEllipsis)
        {
            //有省略号
            textList = Canvas2DHelper.GetLineBreakWithOverflowEllipsisTextList(canvas, text, fontSize, fontWeight, lineWidth, 1);
        }
        else
        {
            //无省略号
            textList = [text];
        }
    }

    //循环绘制文本
    for (let i = 0; i < textList.length; i++)
    {
        const rowText = textList[i];
        let rowTextCanvas = canvasContext.measureText(rowText);
        let rowTextWidth = rowTextCanvas.width;
        lineWidthList.push(rowTextWidth);

        let rowY = y + (i * lineHeight);
        //绘制文本
        canvasContext.fillText(rowText, x, rowY);
    }

    //最大行宽
    maxLineWidth = Math.max(lineWidthList);

    return maxLineWidth;
}

/**
 * 获取换行字符串
 * @param canvas 
 * @param text 文本
 * @param fontSize 字体大小
 * @param fontWeight 字体粗细
 * @param lineWidth 行宽
 * @returns 
 */
private static GetLineBreakTextList(canvas: Canvas, text: string, fontSize: number, fontWeight: number, lineWidth: number): Array<string>
{
    let textList: Array<string> = [];

    let canvasContext = canvas.getContext("2d");
    //字体大小
    canvasContext.font = `${fontWeight} ${fontSize}px sans-serif`;

    let textLength = text.length;
    let entireText = text;
    let currentRowText = "";
    for (let i = 0; i < text.length; i++)
    {
        let textItem = text[i];
        currentRowText += textItem;
        let currentRowTextCanvas = canvasContext.measureText(currentRowText);
        let currentRowTextWidth = currentRowTextCanvas.width;

        if (currentRowTextWidth > lineWidth)
        {
            currentRowText = currentRowText.substring(0, currentRowText.length - 1);
            //换行
            textList.push(currentRowText);
            //初始化行数据
            currentRowText = "";
            i--;
            entireText = text.substring(i, textLength);
        }
    }

    //最后一行
    textList.push(currentRowText);

    return textList;
}

/**
 * 获取换行省略号字符串
 * @param canvas 
 * @param text 文本
 * @param fontSize 字体大小
 * @param fontWeight 字体粗细
 * @param lineWidth 行宽
 * @param lineCount 最大行数
 * @returns 
 */
private static GetLineBreakWithOverflowEllipsisTextList(canvas: Canvas, text: string, fontSize: number = 14, fontWeight: number, lineWidth: number, lineCount: number = 1): Array<string>
{
    let textList: Array<string> = [];
    let canvasContext = canvas.getContext("2d");
    //字体大小
    canvasContext.font = `${fontWeight} ${fontSize}px sans-serif`;

    let isAddLast = true;
    let textLength = text.length;
    let entireText = text;
    let currentRowText = "";
    for (let i = 0; i < text.length; i++)
    {
        let textItem = text[i];
        currentRowText += textItem;
        let currentRowTextCanvas = canvasContext.measureText(currentRowText);
        let currentRowTextWidth = currentRowTextCanvas.width;

        if (currentRowTextWidth > lineWidth)
        {
            currentRowText = currentRowText.substring(0, currentRowText.length - 1);
            let currentRowCount = textList.length + 1;
            if (currentRowCount < lineCount)
            {
                //换行
                textList.push(currentRowText);
                //初始化行数据
                currentRowText = "";
                i--;
                entireText = text.substring(i, textLength);
            }
            else
            {
                let lastRowText = Canvas2DHelper.GetLineBreakWithOverflowEllipsisText(canvas, currentRowText, fontSize, fontWeight, lineWidth);
                textList.push(lastRowText);
                isAddLast = false;
                break;
            }
        }
    }

    if (true == isAddLast)
    {
        textList.push(currentRowText);
    }

    return textList;
}

/**
 * 获取增加省略号之后的字符串
 * @param canvas 
 * @param text 文本
 * @param fontSize 字体大小
 * @param fontWeight 字体粗细
 * @param lineWidth 行宽
 * @returns 
 */
private static GetLineBreakWithOverflowEllipsisText(canvas: Canvas, text: string, fontSize: number = 14, fontWeight: number, lineWidth: number): string
{
    let ellipsisText = "";
    let canvasContext = canvas.getContext("2d");
    //字体大小
    canvasContext.font = `${fontWeight} ${fontSize}px sans-serif`;

    let ellipsis = "...";
    let ellipsisLength = ellipsis.length;
    let currentRowText = text;

    for (let i = text.length - 1; i >= 0; i--)
    {
        const textItem = text[i];

        currentRowText += ellipsis;

        let currentRowTextCanvas = canvasContext.measureText(currentRowText);
        let currentRowTextWidth = currentRowTextCanvas.width;
        if (currentRowTextWidth > lineWidth)
        {
            //删去一个字符
            currentRowText = text.substring(0, i + 1);
        }
        else
        {
            ellipsisText = currentRowText;
            break;
        }
    }

    return ellipsisText;
}

绘制iconfont

这里就很难蚌了,在html里面我们只需要把字体改成iconfont.css里面的,然后用\u+Unicode16进制就行,但是小程序这一坨不支持绘制iconfont,这个特性从canvas刚出来就有人说了,到现在都没有
所以我们直接绘制png图片吧,当然这里就需要注意了,因为图片的缩放会模糊,所以建议自己在cdn里面搞对应的尺寸

export class CreateIconFontOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }

    private _iconUrl: string = "";
    /**
     * 图标链接
     */
    public get IconUrl(): string
    {
        return this._iconUrl;
    }
    public set IconUrl(v: string)
    {
        this._iconUrl = v;
    }

    private _fontSize: number = 14;
    /**
     * 字体大小
     */
    public get FontSize(): number
    {
        return this._fontSize;
    }
    public set FontSize(v: number)
    {
        this._fontSize = v;
    }

    private _fontColor: string = "#000000";
    /**
     * 字体颜色
     */
    public get FontColor(): string
    {
        return this._fontColor;
    }
    public set FontColor(v: string)
    {
        this._fontColor = v;
    }

    private _x: number = 0;
    /**
     * 坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }

    private _y: number = 0;
    /**
     * 坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }

    constructor()
    {
        this.Canvas = null;
        this.IconUrl = "";
        this.FontColor = "#000000";
        this.FontSize = 14;
        this.X = 0;
        this.Y = 0;
    }

}

还要注意这个是异步函数,要await

/**
 * 绘制 iconfont 图标
 * @param option 
 * @returns 
 */
public static CreateIconFont(option: CreateIconFontOptionModel): Promise<void>
{
    return new Promise((resolve, reject) =>
    {

        let canvas = option.Canvas;
        let fontSize = option.FontSize;
        let fontColor = option.FontColor;
        let iconUrl = option.IconUrl;
        let x = option.X;
        let y = option.Y;

        let fontColorRgb = ColorHelper.HexColorToRgb(fontColor);
        let fontColorRed = fontColorRgb.Red;
        let fontColorGreen = fontColorRgb.Green;
        let fontColorBlue = fontColorRgb.Blue;

        //加载图标
        let iconCanvas = Canvas2DHelper.CreateOffscreenCanvas();
        let iconImage = iconCanvas.createImage();
        iconImage.onload = () =>
        {
            let iconImageHeight = iconImage.height;
            let iconImageWidth = iconImage.width;

            let zoomRatio = iconImageHeight / fontSize;
            let iconCanvasHeight = Math.round(iconImageHeight / zoomRatio);
            let iconCanvasWidth = Math.round(iconImageWidth / zoomRatio);

            iconCanvas.height = iconCanvasHeight;
            iconCanvas.width = iconCanvasWidth;
            let iconCanvasContext = iconCanvas.getContext("2d");
            iconCanvasContext.drawImage(iconImage, 0, 0, iconCanvas.width, iconCanvas.height);

            let unSetColorIconData = iconCanvasContext.getImageData(0, 0, iconCanvas.width, iconCanvas.height);
            let unSetColorIconPixelList = unSetColorIconData.data;
            for (let i = 0; i < unSetColorIconPixelList.length; i += 4)
            {
                let unSetColorIconPixelRed = unSetColorIconPixelList[i];
                let unSetColorIconPixelGreen = unSetColorIconPixelList[i + 1];
                let unSetColorIconPixelBlue = unSetColorIconPixelList[i + 2];
                let unSetColorIconPixelAlpha = unSetColorIconPixelList[i + 3];

                if (0 != unSetColorIconPixelAlpha)
                {
                    unSetColorIconPixelList[i] = fontColorRed;
                    unSetColorIconPixelList[i + 1] = fontColorGreen;
                    unSetColorIconPixelList[i + 2] = fontColorBlue;
                }
            }

            iconCanvasContext.putImageData(unSetColorIconData, 0, 0);

            let canvasContext = canvas.getContext("2d");

            //绘制图标
            canvasContext.drawImage(iconCanvas, x, y);

            resolve("图片渲染完成");
        };
        iconImage.onerror = () =>
        {
            reject("图片加载失败");
        };
        iconImage.src = iconUrl;
    });
}

绘制图片

这里我们需要注意图片的原点坐标和缩放模式
先搞一个缩放模式的枚举出来

export enum CreateImageMode
{
    /**
     * 保持长宽比,添加黑边
     */
    Contain = 0,
    /**
     * 保持长宽比,裁切
     */
    Cover = 2,
    /**
     * 填充,拉伸
     */
    Fill = 4,
    /**
     * 保持原有尺寸
     */
    None = 8,
}

然后再加配置

import { Canvas } from "@tarojs/taro";
import { CreateImageMode } from "./CreateImageMode";

export class CreateImageOptionModel
{

    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }


    private _imageUrl: string = "";
    /**
     * 图片链接
     */
    public get ImageUrl(): string
    {
        return this._imageUrl;
    }
    public set ImageUrl(v: string)
    {
        this._imageUrl = v;
    }

    private _mode: CreateImageMode = CreateImageMode.None;
    /**
     * 绘制图像模式
     */
    public get Mode(): CreateImageMode
    {
        return this._mode;
    }
    public set Mode(v: CreateImageMode)
    {
        this._mode = v;
    }


    private _height: number = 0;
    /**
     * 图片高度
     */
    public get Height(): number
    {
        return this._height;
    }
    public set Height(v: number)
    {
        this._height = v;
    }


    private _width: number = 0;
    /**
     * 图片宽度
     */
    public get Width(): number
    {
        return this._width;
    }
    public set Width(v: number)
    {
        this._width = v;
    }

    private _x: number = 0;
    /**
     * 坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }

    private _y: number = 0;
    /**
     * 坐标Y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }


    constructor()
    {
        this.Canvas = null;
        this.ImageUrl = "";
        this.Mode = CreateImageMode.None;
        this.Height = 0;
        this.Width = 0;
        this.X = 0;
        this.Y = 0;
    }
}

最后是函数,也是异步的

/**
 * 绘制图片
 * @param option 
 * @returns 
 */
public static CreateImage(option: CreateImageOptionModel): Promise<void>
{
    return new Promise((resolve, reject) =>
    {
        let canvas = option.Canvas;
        let imageUrl = option.ImageUrl;
        let mode = option.Mode;
        let height = option.Height;
        let width = option.Width;
        let x = option.X;
        let y = option.Y;

        let canvasWidth = canvas.width;
        let canvasHeight = canvas.height;

        let proportion = width / height;

        let originX = Math.round(canvasWidth / 2);
        let originY = Math.round(canvasHeight / 2);

        let imageCanvas = Canvas2DHelper.CreateOffscreenCanvas();
        let image = imageCanvas.createImage();
        image.onload = () =>
        {
            let imageHeight = image.height;
            let imageWidth = image.width;
            let imageProportion = imageWidth / imageHeight;

            //图片处理后大小
            let createImageHeight = 0;
            let createImageWidth = 0;
            //图片处理后坐标变化
            let createImageOriginX = 0;
            let createImageOriginY = 0;
            let canvasContext = canvas.getContext("2d");
            switch (mode)
            {
                case CreateImageMode.Contain:
                    if (proportion >= imageProportion)
                    {
                        //以高为准
                        createImageHeight = height;
                        let heightZoomRatio = height / imageHeight;
                        createImageWidth = Math.round(imageWidth * heightZoomRatio);
                    }
                    else
                    {
                        //以宽为准
                        createImageWidth = width;
                        let widthZoomRatio = width / imageWidth;
                        createImageHeight = Math.round(imageHeight * widthZoomRatio);
                    }

                    createImageOriginX = x - Math.round(createImageWidth / 2);
                    createImageOriginY = y - Math.round(createImageHeight / 2);


                    //改变坐标系原点
                    canvasContext.translate(originX, originY);

                    //绘制图片
                    canvasContext.drawImage(image, createImageOriginX, createImageOriginY, createImageWidth, createImageHeight);
                    break;
                case CreateImageMode.Cover:
                    let publicZoomRatio = 0;
                    if (proportion >= imageProportion)
                    {
                        //以高为准
                        createImageHeight = height;
                        publicZoomRatio = height / imageHeight;
                        createImageWidth = Math.round(imageWidth * publicZoomRatio);
                    }
                    else
                    {
                        //以宽为准
                        createImageWidth = width;
                        publicZoomRatio = width / imageWidth;
                        createImageHeight = Math.round(imageHeight * publicZoomRatio);
                    }

                    createImageOriginX = x - Math.round(createImageWidth / 2);
                    createImageOriginY = y - Math.round(createImageHeight / 2);


                    //改变坐标系原点
                    canvasContext.translate(originX, originY);

                    //绘制图片
                    canvasContext.drawImage(image, createImageOriginX, createImageOriginY, createImageWidth, createImageHeight);

                    break;
                case CreateImageMode.Fill:
                    createImageHeight = height;
                    createImageWidth = width;
                    createImageOriginX = x - Math.round(createImageWidth / 2);
                    createImageOriginY = y - Math.round(createImageHeight / 2);

                    //改变坐标系原点
                    canvasContext.translate(originX, originY);

                    //绘制图片
                    canvasContext.drawImage(image, createImageOriginX, createImageOriginY, createImageWidth, createImageHeight);
                    break;
                case CreateImageMode.None:
                default:
                    createImageHeight = imageHeight;
                    createImageWidth = imageWidth;

                    createImageOriginX = x - Math.round(createImageWidth / 2);
                    createImageOriginY = y - Math.round(createImageHeight / 2);

                    //改变坐标系原点
                    canvasContext.translate(originX, originY);

                    //绘制图片
                    canvasContext.drawImage(image, createImageOriginX, createImageOriginY, createImageWidth, createImageHeight);
                    break;
            }

            resolve("图片渲染完成");
        };
        image.onerror = () =>
        {
            reject("图片加载失败");
        };
        image.src = imageUrl;

    });
}

转为base64图片

这个还是蛮常用的,毕竟十六进制比二进制短

/**
 * 转换为图片
 * @param canvas 
 * @returns 
 */
public static ConvertToImage(canvas: Canvas): string
{
    // let canvasContext = canvas.getContext("2d");

    let imageUrl = canvas.toDataURL();

    return imageUrl;
}

基于路径绘图

这个是自定义操作步骤,所以我们需要封装几个类
首先是声明一个Point类,这个是就是坐标

export class Point
{

    private _x: number = 0;
    /**
     * 坐标x
     */
    public get X(): number
    {
        return this._x;
    }
    public set X(v: number)
    {
        this._x = v;
    }

    private _y: number = 0;
    /**
     * 坐标y
     */
    public get Y(): number
    {
        return this._y;
    }
    public set Y(v: number)
    {
        this._y = v;
    }

    constructor(x: number = 0, y: number = 0)
    {
        this.X = x;
        this.Y = y;
    }

}

然后声明一个枚举,用来确定操作

export enum PathType
{
    /**
     * 点
     */
    Point = 0,
    /**
     * 直线
     */
    Line = 2,
    /**
     * 圆角
     */
    Round = 4,
}

声明一个基类,所有类型的操作都继承这个类

export class Path
{
    private _pathType: PathType = PathType.Point;
    /**
     * 路径类型
     */
    protected get PathType(): PathType
    {
        return this._pathType;
    }
    protected set PathType(v: PathType)
    {
        this._pathType = v;
    }

}

将枚举类型对应声明一个类
点操作类

export class PointPath extends Path
{
    private _point: Point = null;
    /**
     * 坐标
     */
    public get Point(): Point
    {
        return this._point;
    }
    public set Point(v: Point)
    {
        this._point = v;
    }

    constructor()
    {
        super();
        this.PathType = PathType.Point;
        this.Point = null;
    }

}

直线操作类

export class LinePath extends Path
{

    private _point: Point = null;
    /**
     * 坐标
     */
    public get Point(): Point
    {
        return this._point;
    }
    public set Point(v: Point)
    {
        this._point = v;
    }

    constructor()
    {
        super();
        this.PathType = PathType.Line;
        this.Point = null;
    }

}

圆角操作类

export class RoundPath extends Path
{

    private _startPoint: Point = null;
    /**
     * 起始点
     */
    public get StartPoint(): Point
    {
        return this._startPoint;
    }
    public set StartPoint(v: Point)
    {
        this._startPoint = v;
    }


    private _endPoint: Point = null;
    /**
     * 截止点
     */
    public get EndPoint(): Point
    {
        return this._endPoint;
    }
    public set EndPoint(v: Point)
    {
        this._endPoint = v;
    }

    private _radius: number = 0;
    /**
     * 圆角半径
     */
    public get Radius(): number
    {
        return this._radius;
    }
    public set Radius(v: number)
    {
        this._radius = v;
    }

    /**
     *
     */
    constructor()
    {
        super();
        this.PathType = PathType.Round;
        this.StartPoint = null;
        this.EndPoint = null;
        this.Radius = 0;
    }

}

然后就是配置类

export class CreateGraphOptionModel
{
    private _canvas: Canvas = null;
    /**
     * 画布
     */
    public get Canvas(): Canvas
    {
        return this._canvas;
    }
    public set Canvas(v: Canvas)
    {
        this._canvas = v;
    }


    private _pathList: Array<Path> = [];
    /**
     * 路径集合
     */
    public get pathList(): Array<Path>
    {
        return this._pathList;
    }
    public set pathList(v: Array<Path>)
    {
        this._pathList = v;
    }


    constructor()
    {
        this.Canvas = null;
        this.pathList = [];
    }
}

对应的函数

/**
 * 绘制图形
 * @param option 
 */
public static CreateGraph(option: CreateGraphOptionModel): void
{
    let canvas = option.Canvas;
    let pathList = option.pathList;

    if (false == NullHelper.IsNullOrUndefined(pathList) && pathList.length > 0)
    {
        let canvasContext = canvas.getContext("2d");
        //开始路径
        canvasContext.beginPath();
        for (let i = 0; i < pathList.length; i++)
        {
            let pathListItem = pathList[i];
            let pathListItemType = pathListItem.PathType;
            switch (pathListItemType)
            {
                case PathType.Line:
                    let linePath = pathListItem as LinePath;
                    if (false == NullHelper.IsNullOrUndefined(linePath))
                    {
                        canvasContext.lineTo(linePath.Point.X, linePath.Point.Y);
                    }
                    break;
                case PathType.Round:
                    let roundPath = pathListItem as RoundPath;
                    if (false == NullHelper.IsNullOrUndefined(roundPath))
                    {
                        let startPoint = roundPath.StartPoint;
                        let endPoint = roundPath.EndPoint;
                        let radius = roundPath.Radius;
                        canvasContext.arcTo(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y, radius);
                    }
                    break;
                case PathType.Point:
                default:
                    let pointPath = pathListItem as PointPath;
                    if (false == NullHelper.IsNullOrUndefined(pointPath))
                    {
                        canvasContext.moveTo(pointPath.Point.X, pointPath.Point.Y);
                    }
                    break;
            }
        }
        //合并路径
        canvasContext.closePath();
    }
}

描边

export class CreateGraphStrokeColorOptionModel extends CreateGraphOptionModel
{
    private _strokeColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get StrokeColor(): string
    {
        return this._strokeColor;
    }
    public set StrokeColor(v: string)
    {
        this._strokeColor = v;
    }


    private _lineWidth: number = 1;
    /**
     * 描边宽度
     */
    public get LineWidth(): number
    {
        return this._lineWidth;
    }
    public set LineWidth(v: number)
    {
        this._lineWidth = v;
    }

    constructor()
    {
        super();
        this.StrokeColor = "#000000";
        this.LineWidth = 1;
    }

}
/**
 * 绘制图形,描边
 * @param option 
 */
public static CreateGraphStrokeColor(option: CreateGraphStrokeColorOptionModel): void
{
    let canvas = option.Canvas;
    let pathList = option.pathList;
    let strokeColor = option.StrokeColor;
    let lineWidth = option.LineWidth;

    //绘制图形
    Canvas2DHelper.CreateGraph(option);

    let canvasContext = canvas.getContext("2d");

    //描边宽度
    canvasContext.lineWidth = lineWidth;
    //选择描边颜色
    canvasContext.strokeStyle = color;
    //描边
    canvasContext.stroke();
}

填充颜色

export class CreateGraphFillBackgroundColorOptionModel extends CreateGraphOptionModel
{
    private _backgroundColor: string = "#000000";
    /**
     * 描边颜色
     */
    public get BackgroundColor(): string
    {
        return this._backgroundColor;
    }
    public set BackgroundColor(v: string)
    {
        this._backgroundColor = v;
    }

    constructor()
    {
        super();
        this.BackgroundColor = "#000000";
    }

}
/**
 * 绘制图形,填充颜色
 * @param option 
 */
public static CreateGraphFillBackgroundColor(option: CreateGraphFillBackgroundColorOptionModel): void
{
    let canvas = option.Canvas;
    let pathList = option.pathList;
    let backgroundColor = option.BackgroundColor;

    //绘制图形
    Canvas2DHelper.CreateGraph(option);

    let canvasContext = canvas.getContext("2d");

    //选择背景色
    canvasContext.fillStyle = backgroundColor;
    //填充背景色
    canvasContext.fill();
}

Canvas 封装 结束

posted @ 2024-03-12 10:49  .NET好耶  阅读(9)  评论(0编辑  收藏  举报