Начертайте контур извън повърхността на пътя

Имам следния код за рисуване на фигури (използван главно за правоъгълници), но изглежда, че функциите за рисуване на HTML5 рисуват граници с дебелина, центрирана върху посочените линии. Бих искал да има граница извън повърхността на формата и съм на загуба.

Path.prototype.trace = function(elem, closePath) {
  sd.context.beginPath();
  sd.context.moveTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
  sd.context.lineCap = "square";

  for(var i=1; i<this.points.length; ++i) {
    sd.context.lineTo(this.getStretchedX(i, elem.width), this.getStretchedY(i, elem.height));
  }

  if(closePath) {
    sd.context.lineTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
  }
}

getStrechedX и getStretchedY връщат координатите на n-тия връх, след като формата е приложена към зададена ширина, височина и позиция на отместване на елемента.


Благодарение на отговора на Кен Фирстенберг го накарах да работи за правоъгълник, но това решение за съжаление не може да се приложи към други форми.

http://jsfiddle.net/0zq9mrch/

неуспешни триъгълници

Тук начертах две "широки" граници, като една изважда половината от ширината на линията за всяка позиция, а друга добавя. Не работи (както се очаква), защото само ще постави дебелите линии отгоре и отляво в един случай, под и отдясно в друг - не "извън" формата. Можете също да видите бяла зона около склона.


Опитах се да разбера как мога да накарам върховете да начертаят ръчно пътя за дебелата граница (използвайки fill() вместо stroke()).

дебел триъгълник с показани върхове

Но се оказва, че все още се сблъсквам със същия проблем: как програмно да определя дали ръбът е отвътре или отвън. Това ще изисква малко тригонометрия и тежък алгоритъм. За целите на сегашната ми работа това е твърде голям проблем. Исках да използвам това, за да начертая карта на сграда. Стените на помещението трябва да бъдат начертани извън дадените размери, но засега ще се придържам към самостоятелни наклонени стени.


person Domino    schedule 15.04.2015    source източник
comment
Вие просто рисувате линии, няма концепция за вътрешна или външна линия. Вярвам, че ще трябва сам да се справиш с това и да изместиш линията с 0,5 ширината на линията в каквато посока искаш.   -  person James Montagne    schedule 15.04.2015


Отговори (2)


Решение

Можете да разрешите това, като начертаете две линии:

  • Първа линия с дебелина на линията според предвиденото
  • Втората линия се свива с 50% от ширината на външната линия

За да свиете, добавете 50% към x и y, извадете ширината на линията (или 2x 50%) от ширината и височината.

Пример

щракане

var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var lw50 = lineWidth * 0.5;

// outer line
ctx.lineWidth = lineWidth;         // intended line width
ctx.strokeStyle = "#975";          // color for main line
ctx.strokeRect(40, 40, 100, 100);  // full line

// inner line
ctx.lineWidth = 2;                 // inner line width
ctx.strokeStyle = "#000";          // color for inner line

ctx.strokeRect(40 + lw50, 40 + lw50, 100 - lineWidth, 100 - lineWidth);
<canvas></canvas>

Сложни форми

сложен комплекс

За по-сложни форми ще трябва да изчислите пътя ръчно. Това е малко по-сложно и може би твърде широко за SO. Трябва да имате предвид неща като допирателни, ъгъл при завои, пресичане и така нататък.

Един от начините за измама е:

  • начертайте основната линия с пълна дебелина върху платното
  • след това използвайте отново пътя като изрязваща маска
  • променете композитния режим на дестинация-отгоре
  • начертайте формата с изместване в различни посоки
  • възстановяване на изрязване
  • променете цвета и използвайте отново пътя за основната линия.

Стойността offset по-долу ще определи дебелината на вътрешната линия, докато directions ще определи разделителната способност.

var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var offset = 0.5;                                   // line "thickness"
var directions = 8;                                 // increase to increase details
var angleStep = 2 * Math.PI / 8;

// shape
ctx.lineWidth = lineWidth;                          // intended line width
ctx.strokeStyle = "#000";                           // color for inner line
ctx.moveTo(50, 100);                                // some random shape
ctx.lineTo(100, 20);
ctx.lineTo(200, 100);
ctx.lineTo(300, 100);
ctx.lineTo(200, 200);
ctx.lineTo(50, 100);
ctx.closePath();
ctx.stroke();

ctx.save()

ctx.clip();                                         // set as clipping mask
ctx.globalCompositeOperation = "destination-atop";  // draws "behind" existing drawings

for(var a = 0; a < Math.PI * 2; a += angleStep) {
  ctx.setTransform(1,0,0,1, offset * Math.cos(a), offset * Math.sin(a));
  ctx.drawImage(ctx.canvas, 0, 0);
}

ctx.restore();                              // removes clipping, comp. mode, transforms

// set new color and redraw same path as previous
ctx.strokeStyle = "#975";                           // color for inner line
ctx.stroke();
<canvas height=250></canvas>

person Community    schedule 15.04.2015
comment
Всъщност искам обратното (формата се определя от черната линия и след това кафявата се добавя отвън), но схващам идеята. Ще пробвам да видя какво ще ми даде с други форми. - person Domino; 15.04.2015
comment
@JacqueGoupil това ще работи само с прости/правоъгълни форми. Ако имате нужда от по-сложни форми, това може да ви помогне - нарисувайте формата на платно извън екрана, след което използвайте подобен подход като тук: stackoverflow.com/ въпроси/25467349/ - person ; 15.04.2015
comment
Поздравления за Кен Фирстенберг! Благодаря много. - person Domino; 16.04.2015

Закъснях за партито, но ето един алтернативен начин за „външен инсулт“ по сложен път.

Той използва PathObject, за да опрости процеса на създаване на външен щрих.

PathObject запазва всички команди и аргументи, използвани за дефиниране на вашия сложен път.

Този PathObject може също така да възпроизведе командите - и по този начин може да предефинира/преначертае запазения път.

Класът PathObject може да се използва повторно. Можете да го използвате, за да запазите всеки път (прост или сложен), който трябва да преначертаете.

Html5 Canvas скоро ще има свой собствен Path2D обект, вграден в контекста, но моят пример по-долу има многобраузърно запълване, което може да се използва, докато обектът Path2D не бъде внедрен.

Илюстрация на облак със сребърна подплата, нанесена чрез външен щрих.

въведете описание на изображението тук

„Ето как се прави...“

  • Създайте PathObject, който може да запише всички команди и аргументи, използвани за дефиниране на вашия сложен път. Този PathObject може също така да възпроизвежда командите--и по този начин може да предефинира запазения път. Html5 Canvas скоро ще има свой собствен Path2D обект, вграден в контекста, но моят пример по-долу е многобраузърно попълване, което може да се използва, докато Path2D обектът не бъде внедрен.

  • Запазете сложен път с помощта на PathObject.

  • Възпроизвеждане на командите за пътя на основното платно и запълване/чертане по желание.

  • Възпроизвеждане на командите за пътя върху временно платно в паметта.

  • На временното платно:

    • Задайте context.lineWidth два пъти по-голяма от желаната външна ширина на линията и направете линията.

    • Задайте globalCompositeOperation='destination-out' и попълнете. Това ще накара вътрешността на сложния път да бъде изчистена и прозрачна.

  • Начертайте временното платно върху основното платно. Това кара съществуващата ви сложна пътека върху основното платно да получи „външния щрих“ от платното в паметта.

Ето примерен код и демонстрация:

        function log(){console.log.apply(console,arguments);}

        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
        var canvas1=document.getElementById("canvas1");
        var ctx1=canvas1.getContext("2d");


// A "class" that remembers (and can replay) all the 
// commands & arguments used to define a context path
var PathObject=( function(){

    // Path-related context methods that don't return a value
    var methods = ['arc','beginPath','bezierCurveTo','clip','closePath',
      'lineTo','moveTo','quadraticCurveTo','rect','restore','rotate',
      'save','scale','setTransform','transform','translate','arcTo'];

    var commands=[];
    var args=[];

    function PathObject(){       
        // add methods plus logging
        for (var i=0;i<methods.length;i++){   
            var m = methods[i];
            this[m] = (function(m){
                return function () {
                    if(m=='beginPath'){
                        commands.length=0;
                        args.length=0;
                    }
                    commands.push(m);
                    args.push(arguments);
                    return(this);
            };}(m));
        }
        
        
    };

    // define/redefine the path by issuing all the saved
    //     path commands to the specified context
    PathObject.prototype.definePath=function(context){
        for(var i=0;i<commands.length;i++){
            context[commands[i]].apply(context, args[i]);            
        }
    }   

    //
    PathObject.prototype.show=function(){
        for(var i=0;i<commands.length;i++){
            log(commands[i],args[i]);
        }
    }

    //
    return(PathObject);
})();




var x=75;
var y=100;
var scale=0.50;

// define a cloud path
var path=new PathObject()
.beginPath()
.save()
.translate(x,y)
.scale(scale,scale)
.moveTo(0, 0)
.bezierCurveTo(-40,  20, -40,  70,  60,  70)
.bezierCurveTo(80,  100, 150, 100, 170,  70)
.bezierCurveTo(250,  70, 250,  40, 220,  20)
.bezierCurveTo(260, -40, 200, -50, 170, -30)         
.bezierCurveTo(150, -75,  80, -60,  80, -30)
.bezierCurveTo(30,  -75, -20, -60,   0,   0)
.restore();


// fill the blue sky on the main canvas
ctx.fillStyle='skyblue';
ctx.fillRect(0,0,canvas.width,canvas.height);

// draw the cloud on the main canvas
path.definePath(ctx);
ctx.fillStyle='white';
ctx.fill();
ctx.strokeStyle='black';
ctx.lineWidth=2;
ctx.stroke();

// draw the cloud's silver lining on the temp canvas
path.definePath(ctx1);
ctx1.lineWidth=20;
ctx1.strokeStyle='silver';
ctx1.stroke();
ctx1.globalCompositeOperation='destination-out';
ctx1.fill();

// draw the silver lining onto the main canvas
ctx.drawImage(canvas1,0,0);
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>Main canvas with original white cloud + small black stroke<br>The "outside silver lining" is from the temp canvas</h4>
<canvas id="canvas" width=300 height=300></canvas>
<h4>Temporary canvas used to create the "outside stroke"</h4>
<canvas id="canvas1" width=300 height=300></canvas>

person markE    schedule 16.04.2015
comment
Няма да използвам това решение просто защото изисква да добавя още едно буфериращо платно към моето приложение само за един тип форма, но това е много умно решение. Бонус точки за правене на верижни функции на пътя. - person Domino; 16.04.2015