覆盖上下文。属性和功能

Override context. properties and functions

在创建我的 canvas 并获得 var context = can.getContext('2d') 之后,我的所有 canvas 工作都是通过设置属性或调用 context.

上的方法来完成的

我创建了一个多显示器屏幕截图工具,在单显示器上运行良好。但是现在,我正在处理多个显示器,因此屏幕 top/left 和比例不同。有时(比如在具有自定义 DPI 级别的 Windows 平台上)我必须缩放点。因此,我想通过覆盖函数传递所有属性和方法设置,以便在调用真正的 context.BLAH 之前,它会首先将坐标转换为按当前屏幕坐标缩放和偏移。

我可以缩放上下文,但这确实会导致抗锯齿的视觉问题。

这可能吗?

我尝试覆盖 context.lineWidth 和 context.fillRect 但我遇到了本机访问错误。

我想避免用:

包裹它
function lineWidth(a) {
doConvesionOnA(a)
ctx.lineWidth = a;
}

然后每次通过函数wrap调用。但如果这是唯一的方法,我会把它包起来。在为每个 属性 和方法创建包装之前,我只想先问一下,然后用我的包装替换我所有的 context. 调用。

方法可以,但属性不行,因为这些要经过验证,如果不是有效类型,值将被简单地拒绝,即不能设置函数来替换它(它也不能更新内部设置 if 它可以)。

我建议改用包装器对象。您可以通过绑定方法和包装属性使其与普通上下文兼容:

function MyContext(ctx) {

    // Methods

    this.moveTo = ctx.moveTo.bind(ctx);
    this.lineTo = ctx.lineTo.bind(ctx);
    // etc.

    // Properties

    Object.defineProperty(this, "lineWidth", {
        get: function() {return ctx.lineWidth},
        set: function(value) {
            // do something magic with value
            ctx.lineWidth = value
        }
    });

    // etc.
}

如果您想更改方法的值:

this.moveTo = function(x, y) {
    // alter here
    ctx.moveTo(x, y);
};

你也可以使用apply(),它比传入实际参数更灵活,但速度较慢:

this.arc = function() {
    ctx.arc.apply(ctx, arguments)
};

这可能有点乏味,但让您可以完全控制传递给真实上下文的内容。然后简单地创建一个对象的实例并将其用作 2D 上下文:

var myCtx = new MyContext(ctx);
myCtx.lineTo(100, 100);
myCtx.lineWidth = 20;
...

同意@K3N 包装上下文的建议。

这是我从 CanvasRendingContext2D 记录器中抓取的一些代码,显示了您可以多快开始包装 CanvasRendingContext2D:

function LoggedContext(canvas) {
    var self = this;
    this.canvas=canvas;
    this.context=canvas.getContext('2d');
    this.imageURLs=[];
    this.fillStyles=[];
    this.logs=[];
    this.commands=[];
    this.funcs={};
    this.init(self);
}

LoggedContext.prototype.init 方法中,为每个属性创建 get/set 块,并通过使用 .apply.

LoggedContext.prototype.init=function(self){

    // create get/sets for properties
    var properties=['strokeStyle','lineWidth','font','globalAlpha',
        'globalCompositeOperation','shadowColor','shadowBlur',
        'shadowOffsetX','shadowOffsetY','lineCap','lineJoin',
        'miterLimit','textAlign','textBaseline'];

    for(var i=0;i<properties.length;i++){
        (function(i) {
            Object.defineProperty(self, i, {
                get: function () {
                    return this.context[i];
                },
                set: function (val) {
                    this.log(i,val,true);
                    this.context[i]=val;
                }
            })
        })(properties[i]);
    }

    // create mirror methods that pipe arguments to the real context
    var methods = ['arc','beginPath','bezierCurveTo','clearRect','clip',
      'closePath','fill','fillRect','fillText','lineTo','moveTo',
      'quadraticCurveTo','rect','restore','rotate','save','scale','setTransform',
      'stroke','strokeRect','strokeText','transform','translate','putImageData'];

    for (var i=0;i<methods.length;i++){   
        var m = methods[i];
        this[m] = (function(m){
            return function () {
                this.context[m].apply(this.context, arguments);
                this.log(m,arguments);
                return(this);
        };}(m));
    }

    // mirror methods that have return values
    var returnMethods = ['measureText','getImageData','toDataURL',
      'isPointInPath','isPointInStroke','createImageData'];

    for (var i=0;i<returnMethods.length;i++){   
        var m = returnMethods[i];
        this[m] = (function(m){
            return function () {
                return(this.context[m].apply(this.context, arguments));
        };}(m));
    }

    // In this example code ...
    // These Properties & Methods requiring special handling have 
    // been removed for brevity & clarity
    //
    //  .fillStyle
    //  .strokeStyle
    //  .drawImage
    //  .createLinearGradient
    //  .createRadialGradient
    //  .createPattern


} // end init()

所有 属性 get/set 和所有方法调用都通过 LoggedContext.prototype.log 方法进行引导。

为了您的目的,您可以在 get/set 块中进行调整,也可以方便地在 .log 方法中进行调整,因为所有内容都通过 .log 方法进行传输。

LoggedContext.prototype.log=function(command,Args,isProperty){
    var commandIndex=this.commands.indexOf(command);
    if(commandIndex<0){
        this.commands.push(command);
        commandIndex=this.commands.length-1
    }
    if(isProperty){
        this.logs.push([commandIndex,Args]);
    }else{
        this.logs.push([commandIndex,Array.prototype.slice.call(Args)]);
    }
}