SVG:反向三次方和二次贝塞尔曲线

问题描述

我正在为reverse the draw direction的SVG路径命令编写脚本,到目前为止,一切都工作正常,但无法使用S路径命令或T

my implementations中仅用于三次贝塞尔曲线C的路径反向功能中,它可以完美地工作,但是输出路径字符串相当大,有时长度是其两倍或三倍。

这是reversePath.js的简化版本,到目前为止,它为S和测试页实现了一些基本处理:

  // the script I'm working on works with these arrays
  var path = [['M',10,80],['C',40,65,95,['S',150,180,230,270,80]],target = document.getElementById('target');

// our focus is RIGHT HERE
function reversePath(pathInput){
  let isClosed = pathInput.slice(-1)[0][0] === 'Z',params = {x1: 0,y1: 0,x2: 0,y2: 0,x: 0,y: 0,qx: null,qy: null},pathCommand = '',pLen = 0,reversedpath = [];

  reversedpath = pathInput.map((seg,i,pathArray)=>{
    pLen = pathArray.length
    pathCommand = seg[0]

    switch(pathCommand){
      case 'M':
        x = seg[1]
        y = seg[2]
        break
      case 'Z':
        x = pathArray[0][1]
        y = pathArray[0][2]
        break
      default:
        x = seg[seg.length - 2]
        y = seg[seg.length - 1]
    }

    return {
      c: pathCommand,x: x,y: y,seg: seg
    }
  }).map((seg,pathArray)=>{
    let segment = seg.seg,prevSeg = i && pathArray[i-1],nextSeg = pathArray[i+1] && pathArray[i+1],result = []

    pLen = pathArray.length
    pathCommand = seg.c
    
    params.x = i ? pathArray[i-1].x : pathArray[pLen-1].x
    params.y = i ? pathArray[i-1].y : pathArray[pLen-1].y

    switch(pathCommand){
      case 'M':
        result = isClosed ? ['Z'] : [pathCommand,params.x,params.y]
        break
      case 'C':
        if ('S' === nextSeg.c) {
          params.x2 = params.x1 + params.x2 / 2
          params.y2 = params.y1 + params.y2 / 2
          result = ['S',params.x2,params.y2,params.y]
        } else {
          params.x1 = segment[3]
          params.y1 = segment[4]
          params.x2 = segment[1]
          params.y2 = segment[2]
          result = [pathCommand,params.x1,params.y1,params.y];
        }
        break
      case 'S':
        params.x2 = params.x1 + params.x2 / 2
        params.y2 = params.y1 + params.y2 / 2

        if (nextSeg && 'S' === nextSeg.c) {
          result = [pathCommand,params.y]
        } else {
          params.x1 = params.x1 + params.x2 / 2
          params.y1 = params.y1 + params.y2 / 2
          params.x2 = segment[1];
          params.y2 = segment[2];
          result = ['C',params.y];
        }
        break
      case 'Z':
        result = ['M',params.y]
        break
      default:
        result = segment.slice(0,-2).concat([params.x,params.y])
    }    
    return result
  })
  return isClosed ? reversedpath.reverse() : [reversedpath[0]].concat(reversedpath.slice(1).reverse())
}

function pathToString(pathArray) {
  return pathArray.map(x=>x[0].concat(x.slice(1).join(' '))).join(' ')
}

function reverse(){
  var reversed = pathToString(reversePath(path));
  target.setAttribute('d',reversed)
  target.closest('.col').innerHTML += '<br><p class="text-left">'+reversed+'</p>'
}
.row {width: 100%; display: flex; flex-direction: row}
.col {width: 50%; text-align: center}
.text-left {text-align: left}
<button onclick="reverse()">REVERSE</button>
<hr>
<div class="row">
  <div class="col">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
      <path id="example" d="M10 80 C40 10,65 10,95 80S 150 150,180 80S 230 10 270 80" stroke="green" stroke-width="2" fill="transparent" />
    </svg>
    normal path
  </div>
  
  <div class="col">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
      <path id="target" d="M0 0L0 0" stroke="orange" stroke-width="2" fill="transparent" />
    </svg>
    reversed path (click the button)
  </div>
</div>

我从Raphael.js实现开始,将S和Q和T路径命令转换为C(cubicBezier),思考和进行逆向工程也许可以找到一种使之工作的方法。 / p>

因此,在反转形状时,我需要一点帮助来为这些ST路径命令找出正确的公式。如果有人可以帮助我S,我可以在T上找到自己。

感谢您的回复

解决方法

好的,归一化可以解决问题。还有一些新增功能和更强大的值处理功能,但让我们开始吧,这是具有新增功能的更新功能:

// the script I'm working on works with these arrays
  var pathCubic = [['M',10,80],['C',40,65,95,['S',150,180,230,270,80]],targetCubic = document.getElementById('target');

// the updated function
function reversePath(absolutePath){
  var isClosed = absolutePath.slice(-1)[0][0] === 'Z',reversedPath = normalizePath(absolutePath).map(function (segment,i){
    return {
      c: absolutePath[i][0],x: segment[segment.length - 2],y: segment[segment.length - 1],seg: absolutePath[i],normalized: segment
    }
  }).map(function (seg,i,pathArray){
    var segment = seg.seg,data = seg.normalized,prevSeg = i && pathArray[i-1],nextSeg = pathArray[i+1] && pathArray[i+1],pathCommand = seg.c,pLen = pathArray.length,x = i ? pathArray[i-1].x : pathArray[pLen-1].x,y = i ? pathArray[i-1].y : pathArray[pLen-1].y,result = [];
    switch(pathCommand){
      case 'M':
        result = isClosed ? ['Z'] : [pathCommand,x,y];
        break
      case 'C':
        if (nextSeg && nextSeg.c === 'S') {
          result = ['S',segment[1],segment[2],y];
        } else {
          result = [pathCommand,segment[3],segment[4],y];
        }
        break
      case 'S':
        if ( prevSeg && 'CS'.indexOf(prevSeg.c)>-1 && (!nextSeg || nextSeg && nextSeg.c !== 'S')) {
          result = ['C',data[3],data[4],data[1],data[2],y];
        }
        break
      case 'Z':
        result = ['M',y];
        break
    }
    return result
  });
  return isClosed ? reversedPath.reverse() : [reversedPath[0]].concat(reversedPath.slice(1).reverse())
}

// new additions
function shorthandToCubic(x1,y1,x2,y2,prevCommand){
  return 'CS'.indexOf(prevCommand)>-1 ? { x1: x1 * 2 - x2,y1: y1 * 2 - y2}
                                      : { x1 : x1,y1 : y1 }
}

function normalizeSegment(segment,params,prevCommand) {
  var nqxy,nxy;
  switch (segment[0]) {
    case "S":
      nxy = shorthandToCubic(params.x1,params.y1,params.x2,params.y2,prevCommand);
      params.x1 = nxy.x1;
      params.y1 = nxy.y1;
      segment = ["C",nxy.x1,nxy.y1].concat(segment.slice(1));
      break
  }
  return segment
}

function normalizePath(pathArray) {
  var params = {x1: 0,y1: 0,x2: 0,y2: 0,x: 0,y: 0,qx: null,qy: null},allPathCommands = [],pathCommand = '',prevCommand = '',ii = pathArray.length,segment,seglen;
  for (var i = 0; i < ii; i++) {
    pathArray[i] && (pathCommand = pathArray[i][0]);
    allPathCommands[i] = pathCommand;
    i && ( prevCommand = allPathCommands[i - 1]);
    pathArray[i] = normalizeSegment(pathArray[i],prevCommand);
    segment = pathArray[i];
    seglen = segment.length;
    params.x1 = +segment[seglen - 2];
    params.y1 = +segment[seglen - 1];
    params.x2 = +(segment[seglen - 4]) || params.x1;
    params.y2 = +(segment[seglen - 3]) || params.y1;
  }
  return pathArray
}

function pathToString(pathArray) {
  return pathArray.map(x=>x[0].concat(x.slice(1).join(' '))).join(' ')
}

function reverse(){
  var reversedCubic = pathToString(reversePath(pathCubic));
  targetCubic.setAttribute('d',reversedCubic)
  targetCubic.closest('.col').innerHTML += '<br><p class="text-left">'+reversedCubic+'</p>'
}
.row {width: 100%; display: flex; flex-direction: row}
.col {width: 50%; text-align: center}
.text-left {text-align: left}
<button onclick="reverse()">REVERSE</button> Now works with multiple `S` shorthands
<hr>
<div class="row">
  <div class="col">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
      <path id="example" d="M10 80 C40 10,65 10,95 80S 150 150,180 80S 230 10 270 80" stroke="green" stroke-width="2" fill="transparent" />
    </svg>
    normal path
  </div>
  
  <div class="col">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
      <path id="target" d="M0 0L0 0" stroke="orange" stroke-width="2" fill="transparent" />
    </svg>
    reversed CUBIC BEZIER path
  </div>
</div>

您可以检查最新的SVGPathCommander version on npmdemo page。它还可以管理多个T路径命令,没有问题。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...