使用Fabric JS模拟字距调整以获取位图字体 [英] Simulate kerning for a bitmapped font with Fabric JS

查看:161
本文介绍了使用Fabric JS模拟字距调整以获取位图字体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Fabric JS创建一种效果,在这种效果中,字母看上去像是绣"的.在这样的毛衣上:

我可以通过使用

我该如何实现?有更好的方法使字距调整正常工作吗?

解决方案

使用破折号作为针迹和 ctx.globalCompositeOperation ="source-atop" 仅在文本内部绘制.改变笔触宽度以从字体的内部构造针迹.

不幸的是,行划线间距仅对于笔划中心有效,因此该方法适用于某些字符,但不适用于所有字符.

可以改进,因为没有努力使针脚变圆(每针的高光和阴影颜色),但是由于无法控制线连接处针脚的位置,所以我看不到进一步改进针尖的目的.

可以使用任何字体.

有关示例和代码,请参见代码段.

 函数stitchIt(文本,s​​titchLen,stitchOffset,threadThickness,大小,字体,col1,col2,shadowColor,偏移量,模糊){const can = document.createElement("canvas");const ctx = can.getContext("2d");ctx.font = size +"px" + font;const width = ctx.measureText(text).width;can.width =宽度;can.height =大小;ctx.font = size +"px" + font;ctx.textAlign =中心";ctx.textBaseline =中间";ctx.globalCompositeOperation =源于";ctx.lineCap =对接";ctx.lineJoin =斜角";ctx.fillStyle = col2;ctx.setTransform(1,0,0,1,width/2,size/2);ctx.fillText(text,0,0);ctx.setLineDash([stitchLen,stitchLen]);var w = size,off = 0;ctx.globalCompositeOperation =源于顶部"而(w> 0){ctx.lineWidth = w;ctx.strokeStyle = col1;ctx.lineDashOffset =关闭;ctx.strokeText(text,0,0);如果(w> threadThickness){w-= threadThickness/2;ctx.lineWidth = w;ctx.lineDashOffset = off +缝线长度;ctx.strokeStyle = col2;ctx.strokeText(text,0,0);off + =针距* *针距偏移;w-= threadThickness/2;} 别的 {休息;}}ctx.globalCompositeOperation =目标输出";ctx.globalAlpha = 0.5;ctx.strokeStyle = col2;ctx.lineWidth = threadThickness/2;ctx.lineDashOffset = off +缝线长度;ctx.strokeText(text,0,0);ctx.globalCompositeOperation =目标结束";ctx.save();ctx.shadowColor =#000";ctx.shadowOffsetX =偏移量;ctx.shadowOffsetY =偏移量;ctx.shadowBlur =模糊;ctx.fillText(text,0,0);ctx.restore();ctx.globalCompositeOperation =源于";还可以}ctx = canvas.getContext("2d");textEl.addEventListener("input",update);sLenEl.addEventListener("change",更新);sOffEl.addEventListener("change",update);sThreadEl.addEventListener("change",update);sFontEl.addEventListener("change",更新);colEl.addEventListener("click",()=> {color = colors [colIdx ++%colors.length];更新();});//更新反跳渲染var tHdl;函数update(){clearTimeout(tHdl);tHdl = setTimeout(draw,200);}const colors = [[[#DDD",#888"],[#FFF",#666"],[#F88",#338"],[#8D8",#333"]];var colIdx = 0;var color = colors [colIdx ++];itchIt("STITCH",5,1.4,4,160,"Arial Black",#DDD",#888",#0004",4,5);函数draw(){ctx.clearRect(0,0,1500,180);如果(textEl.value){const image = itchItIt(textEl.value,数字(sLenEl.value),sOffEl.value/100 + 0.8,编号(sThreadEl.value),编号(sFontEl.value),"Arial黑色",颜色[0],颜色[1],#0004",4,5);ctx.drawImage(image,0,90-image.height/2);}}draw();  

  canvas {背景:#49b;边框:2px实线#258;位置:绝对;顶部:0px;左:230px;}  

 < div>< input id ="textEl" type ="text" value ="STITCH"></input>< br>< button id ="colEl">更改颜色</button>< br><输入id ="sLenEl" type ="range" min ="2" max ="16" value ="5">< label for ="sLenEl">针迹长度</label>< br><输入id ="sOffEl" type ="range" min ="0" max ="100" value ="50">< label for ="sOffEl">针距</label>< br><输入id ="sThreadEl" type ="range" min ="2" max ="10" value ="4">< label for ="sThreadEl">线程大小</label>< br><输入id ="sFontEl" type ="range" min ="20" max ="180" value ="140">< label for ="sFontEl">字体大小</label>< br></div>< canvas id ="canvas" width ="1500" height ="180"></canvas>  

I am trying to create an effect with Fabric JS where letters appear to be "embroidered" on a sweater like this:

I can achieve this effect in Photoshop by using this action.

My idea for getting it into a <canvas> is to render out a png from Photoshop of every embroidered letter. Then, I will take each letter and place it on the canvas based on what the user types.

However this approach will not have correct kerning.

To fix this, I was trying to write out text in Fabric using the same font and then overlay each embroidered png on top of the letter it is replacing (and then hide the text itself).

Here's how I render the text:

window.chest_text = new fabric.IText("NYC", {
      fill: '#000',
      fontSize: 12,
      left: 210,
      top: 100,
      fontFamily: 'Graphik',
      fontWeight: 500,
      lineHeight: 1,
      originX: 'center',
    });

And then here's how I render the embroidered letters:

  var n_url = 'https://res.cloudinary.com/tricot/image/upload/v1598820746/tmp/n-embroidery-test.png'
  var y_url = 'https://res.cloudinary.com/tricot/image/upload/v1598820745/tmp/y-embroidery-test.png'
  var c_url = 'https://res.cloudinary.com/tricot/image/upload/v1598820745/tmp/c-embroidery-test.png'
  
  fabric.Image.fromURL(n_url, function(img) {
    img.set({
      left: Math.round(window.chest_text.aCoords.bl.x),
      top: window.chest_text.top
    })
    
    img.scaleToHeight(Math.floor(window.chest_text.__charBounds[0][0].height / 1.13), true)
    
    canvas.add(img);
  })
  
  fabric.Image.fromURL(y_url, function(img) {
    img.set({
      left: Math.round(window.chest_text.aCoords.bl.x + window.chest_text.__charBounds[0][1].left),
      top: window.chest_text.top
    })
    
    img.scaleToHeight(Math.floor(window.chest_text.__charBounds[0][1].height / 1.13), true)
    
    canvas.add(img);
  })
  
  fabric.Image.fromURL(c_url, function(img) {
    img.set({
      left: Math.round(window.chest_text.aCoords.bl.x + window.chest_text.__charBounds[0][2].left),
      top: window.chest_text.top
    })
    
    img.scaleToHeight(Math.floor(window.chest_text.__charBounds[0][2].height / 1.13), true)
    
    canvas.add(img);
  })
  
  window.chest_text.opacity = 0.5
  
  window.canvas.renderAll()

However I can't get the embroidered letters to EXACTLY overlay the regular text (even though it's the same font):

How can I achieve this? Is there a better way of getting kerning to work correctly?

解决方案

Using line dash as the stitch and ctx.globalCompositeOperation = "source-atop" to draw only inside the text. Vary the stroke width to build stitches from inside of font out.

Unfortunately the line dash spacing is only true for the stroke center so the approach works for some characters but not all.

Can be improved upon as there was no effort to round stitchs (highlight and shadow color per stitch) but as there is no control over positioning of stitches at line joins i could not see the point of refining it further.

Will work with any font.

See snippet for demo and code.

function stitchIt(text, stitchLen, stitchOffset, threadThickness, size, font, col1, col2 , shadowColor, offset, blur) {
    const can = document.createElement("canvas");
    const ctx = can.getContext("2d");
    ctx.font = size + "px "+font;
    const width = ctx.measureText(text).width;
    can.width = width;
    can.height = size;
    ctx.font = size + "px "+font;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.globalCompositeOperation = "source-over";
    ctx.lineCap = "butt";
    ctx.lineJoin = "bevel";
    ctx.fillStyle = col2;
    ctx.setTransform(1,0,0,1,width / 2, size / 2);
    ctx.fillText(text, 0, 0);
    ctx.setLineDash([stitchLen, stitchLen]);
    var w = size, off = 0;
    ctx.globalCompositeOperation = "source-atop"
    while (w > 0) {
        ctx.lineWidth = w;
        ctx.strokeStyle = col1;
        ctx.lineDashOffset = off; 
        ctx.strokeText(text, 0, 0);
        if (w > threadThickness) {
            w -= threadThickness / 2;
            ctx.lineWidth = w;
            ctx.lineDashOffset = off + stitchLen; 
            ctx.strokeStyle = col2;
            ctx.strokeText(text, 0, 0);
            off += stitchLen * stitchOffset;
            w -= threadThickness / 2;
        } else {
            break;
        }
    }
    ctx.globalCompositeOperation = "destination-out";
    ctx.globalAlpha = 0.5;
    ctx.strokeStyle = col2;
    ctx.lineWidth = threadThickness / 2;
    ctx.lineDashOffset = off + stitchLen; 
    ctx.strokeText(text, 0, 0);
    ctx.globalCompositeOperation = "destination-over";
    ctx.save();
    ctx.shadowColor = "#000";
    ctx.shadowOffsetX = offset;
    ctx.shadowOffsetY = offset;
    ctx.shadowBlur = blur;
    ctx.fillText(text, 0, 0);
    ctx.restore();    
    ctx.globalCompositeOperation = "source-over";
    return can;
}
ctx = canvas.getContext("2d");
textEl.addEventListener("input", update);
sLenEl.addEventListener("change", update);
sOffEl.addEventListener("change", update);
sThreadEl.addEventListener("change", update);
sFontEl.addEventListener("change", update);
colEl.addEventListener("click", () => {
    color = colors[colIdx++ % colors.length];
    update();
});

// update debounces render
var tHdl;
function update() {
    clearTimeout(tHdl);
    tHdl = setTimeout(draw, 200);
}
const colors=[["#DDD","#888"],["#FFF","#666"],["#F88","#338"],["#8D8","#333"]];
var colIdx = 0;
var color = colors[colIdx++];
stitchIt("STITCH",5, 1.4, 4, 160,"Arial Black","#DDD","#888","#0004" , 4, 5);
function draw() {
    ctx.clearRect(0,0,1500,180);
    if (textEl.value) {
        const image = stitchIt(
            textEl.value,
            Number(sLenEl.value), 
            sOffEl.value / 100 + 0.8, 
            Number(sThreadEl.value), 
            Number(sFontEl.value),
            "Arial Black",
            color[0],
            color[1],
            "#0004" , 
            4, 
            5
        );
        ctx.drawImage(image,0,90-image.height / 2); 
   }
}
draw();

canvas {
    background: #49b;
    border: 2px solid #258;
    position: absolute;
    top: 0px;
    left: 230px;
}

<div>
    <input id="textEl" type="text" value="STITCH"></input><br>
    <button id="colEl">change Color</button><br>
    
    <input id="sLenEl" type="range" min="2" max="16" value="5"><label for="sLenEl">Stitch length</label><br>
    <input id="sOffEl" type="range" min="0" max="100" value="50"><label for="sOffEl">Stitch offset</label><br>
    <input id="sThreadEl" type="range" min="2" max="10" value="4"><label for="sThreadEl">Thread size</label><br>
    <input id="sFontEl" type="range" min="20" max="180" value="140"><label for="sFontEl">Font size</label><br>
</div>
<canvas id="canvas" width="1500" height="180"></canvas>

这篇关于使用Fabric JS模拟字距调整以获取位图字体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆