1

We are trying to create text art like below images using html5 and canvas but we are not able to find proper parameters to create this kind of design.

  1. text art design parameters
  2. text art design Is there any other parameter required for this type of text art?

Thanks in advance

Ann
  • 25
  • 2
  • 8
  • A technique you need to use is to draw a string char by char rather than a whole, so you can change the context for each char. The first example is easily achieved by linearly increasing the font size while rotating along the *Y* axis and then the reverse. The second one is more tricky, the font size is incremented according to a *cosine* and than *a white wave with the same parameters is draw on the lower/upper/both edge of the text*. Don't get fooled by the second example, font glyphs are not really bent, they just get drawn on top. – Margaret Bloom Mar 02 '16 at 11:18
  • why canvas ? SVG and a set of vectorised to path characters would be much more easy. – Kaiido Mar 02 '16 at 11:24
  • We used this code sample: http://jsfiddle.net/AbdiasSoftware/e8hZy/ – Ann Mar 02 '16 at 11:47
  • Yes and no. To meet your design requirements you must substitute a cubic Bezier curve for the simpler curve used in the linked demo. While it's an excellent demo, the demo does not support cubic Bezier curve distortions -- you'll have to write that code. – markE Mar 03 '16 at 06:25
  • I've done a prototype for replicating your first example [here](https://jsfiddle.net/p2gcwkn4/2/). I can do the second one too, if this is the kind of answer you are looking for. Let me know. – Margaret Bloom Mar 03 '16 at 12:48

2 Answers2

2

Since I like drawing, I implemented the two concept text arts of your question.
Even if it looks like you don't care anymore, this is for anyone who is curious.

Rationale

In order to achieve said effects you need to exploit a technique that is essentially a 2D texture mapping, greatly simplified though, as we are going to keep the left and right edges straight.

Essentially it consist in transforming a square into a different shape1

Texture mapping concept

The blue square represent an image with your text drawn, the red square is how the final image should be "morphed".
By choosing an appropriate morphing you can achieve different kind of object.

Special case

In general texture mapping require some tessellation2 on the shapes being mapped and, possibly, the use of Barycentric coordinate system.

In our case, however, we can make a Strong assumption that we are mapping a rectangle into a new (convex) shape that has the same width as the original and won't change the vertical edges.

Simply put, we are not doing this kind of transformation.

A transformation not allowed

This is important, because we can implement our transformations with a simple algorithm.

Transformation algorithm

Basically we slice the original rectangle into vertical stripes and scale each stripe according to the new shape corresponding stripe height.
For example see, how the height of the middle "stripe" change in the sample mapping of above:

Heights mapping

HTML5 Canvas let us redraw stripes, which actually are columns of pixels, easily with the use of the method drawImage that will takes the form drawImage(source, x, 0, 1, h, x, upperY, 1, lowerY-upperY).
This will take the rectangle corresponding to the stripe at x and draw it at the same x but at a different y and with different height.
Occasionally we may need to take as source a rectangle that is shorter than the whole canvas, but the process is the same.

upperY and lowerY can be computed as a function of x and can take any value. Carefully choosing the functions generating these values will give the desired effect.

1.   For X = 0 To Canvas.Width
1.1     upperY = f_upper(x);
1.2     lowerY = f_lower(x);
1.3     Canvas.DrawImage(SrcCanvs, x, 0, 1, SrcCanvs.Height, x, upperY, 1, lowerY-upperY);

First Art

About implementing

First art

The fiddle is here.

First thing first: We are not mapping the whole canvas, rater a sub rectangle of it that is tall enough to cover the text and large as the canvas.
To parameterize this rectangle we only need its height and its vertical distance from the top of the canvas.
These two parameters are Text height and Text top gap.

The map is

First art mapping

The rest fairly simple: we give the user the choice to set the distance between the tree pair of dots, in Text height cents units, and the horizontal position of the Red pair, in canvas width cents units.

The upper function is the constant function 0.
The lower function is a piece wise function: the first half is used when x is before the middle point, the second half otherwise.
For both case we just compute the line between the lower points: Green-Red for the first half, Red-Blue for the second.

Second art

About implementing

Second art

The fiddle is here.

We let the user choose the amplitude (in Text height cents units) and the frequency (in number of ripples) of the top and the bottom waves along with a scaling parameter for the both waves period (in Canvas width cents units).

I was not able to quickly draw the mapping on InkScape so I let it to the imagination.
Basically we are using sin as the upper and lower function with different parameters.

By changing appropriately the waves parameters all of the four text arts.


Second Art snippet

/// (c) Ken Fyrstenberg Nilsen, Abidas Software .com
/// License: CC-Attribute

var ctx = demo.getContext('2d'),
    font = '64px impact',
    w = demo.width,
    h = demo.height,
    os = document.createElement('canvas'),
    octx = os.getContext('2d');

os.width = w;
os.height = h;

octx.font = font;
octx.textBaseline = 'top';
octx.textAlign = 'center';

function decimalToHex(d, padding) {
    var hex = Number(d).toString(16);
    padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;

    while (hex.length < padding) {
        hex = "0" + hex;
    }

    return hex;
}

function renderArtText() 
{

  var ta = parseInt(iTa.value, 10);
    var tp = parseInt(iTp.value, 10);
    var ba = parseInt(iBa.value, 10);
    var bp = parseInt(iBp.value, 10);
    var yo = parseInt(iYo.value, 10);
    var ww = parseInt(iWw.value, 10);
    var th = parseInt(iTh.value, 10);
    var to = parseInt(iTo.value, 10);
    
    
    vTa.innerHTML = ta;
    vTp.innerHTML = tp;
    vBa.innerHTML = ba;
    vBp.innerHTML = bp;
    vYo.innerHTML = yo;
    vWw.innerHTML = ww;
    vTh.innerHTML = th;
    vTo.innerHTML = to;
    
    octx.clearRect(0, 0, w, h);
    ctx.clearRect(0, 0, w, h);

    octx.fillText(iText.value.toUpperCase(), w * 0.5, 0);
  
  var l = w*ww/100;
  ta = ta*th/100;
    ba = ba*th/100;

  for (var x = 0; x < w; x++)
    {
      var s, f;
        
        s = ta*Math.sin(2*Math.PI*x/l * tp);
        f = ba*Math.sin(2*Math.PI*x/l * bp);

        ctx.drawImage
        (
         os, 
          x, to, 1, th, 
          x, yo-s, 1, th-f+s
        );
    
     
    }
}

iTa.onchange = iTp.onchange  = iBa.onchange = iWw.onchange = 
iBp.onchange = iText.onkeyup = iYo.onchange = 
iTh.onchange = iTo.onchange  = renderArtText;

renderArtText();
span {
    display:inline-block;
    width:120px;
    text-align:right;
    font:12px sans-serif;
}
<canvas id=demo width=400 height=300></canvas>

<br>
<span>Top wave height:</span>
<input id="iTa" type="range" min=-100 max=100 value=0>
<span id="vTa">0</span>

<br>
<span>Top wave ripples:</span>
<input id="iTp" type="range" min=1 max=3 value=1>
<span id="vTp">1</span>

<br>
<span>Bottom wave height:</span>
<input id="iBa" type="range" min=-100 max=100 value=50>
<span id="vBa">50</span>



<br>
<span>Bottom wave ripples:</span>
<input id="iBp" type="range" min=1 max=3 value=1>
<span id="vBp">1</span>

<br>
<span>Waves width:</span>
<input id="iWw" type="range" min=20 max=100 value=80>
<span id="vWw">80</span>

<br>
<span>Offset Y:</span>
<input id="iYo" type="range" min=0 max=100 value=30>
<span id="vYo">30</span>

<br>
<span>Text height:</span>
<input id="iTh" type="range" min=10 max=100 value=60>
<span id="vTh">60</span>

<br>
<span>Text top gap:</span>
<input id="iTo" type="range" min=0 max=30 value=12>
<span id="vTo">12</span>

<br>
<span>Text:</span>
<input id="iText" type="text" value="BRIDGE TEXT">
 

First Art snippet

/// (c) Ken Fyrstenberg Nilsen, Abidas Software .com
/// License: CC-Attribute

var ctx = demo.getContext('2d'),
    font = '64px impact',
    w = demo.width,
    h = demo.height,
    os = document.createElement('canvas'),
    octx = os.getContext('2d');

os.width = w;
os.height = h;

octx.font = font;
octx.textBaseline = 'top';
octx.textAlign = 'center';

function renderArtText() 
{

  var hs = parseInt(iHs.value, 10);
    var hm = parseInt(iHm.value, 10);
    var hf = parseInt(iHf.value, 10);
    var xm = parseInt(iXm.value, 10);
    var yo = parseInt(iYo.value, 10);
    var th = parseInt(iTh.value, 10);
    var to = parseInt(iTo.value, 10);
    
    vHs.innerHTML = hs;
    vHm.innerHTML = hm;
    vHf.innerHTML = hf;
    vXm.innerHTML = xm;
    vYo.innerHTML = yo;
    vTh.innerHTML = th;
    vTo.innerHTML = to;
    
    octx.clearRect(0, 0, w, h);
    ctx.clearRect(0, 0, w, h);

    octx.fillText(iText.value.toUpperCase(), w * 0.5, 0);

    var ysl = (th + hs*th/100)/2;
    var yml = (th + hm*th/100)/2;
  var yfl = (th + hf*th/100)/2;
    
    var xs = 0;
    xm = xm*w/100;
  var xf = w;
  yo = yo*h/100;

  for (var x = 0; x < w; x++)
    {
      var f;
        
      if (x < xm)
          f = ysl + x*(yml-ysl)/(xm-xs);
        else
          f = yml + (x-xm)*(yfl-yml)/(xf-xm);
        
        ctx.drawImage
        (
         os, 
          x, to, 1, th, 
          x, yo, 1, f
        );
    
    }
}

iHs.onchange = iHm.onchange  = iHf.onchange = 
iXm.onchange = iText.onkeyup = iYo.onchange = 
iTo.onchange = iTh.onchange  = renderArtText;

renderArtText();
span {
    display:inline-block;
    width:90px;
    text-align:right;
    font:12px sans-serif;
}
<canvas id=demo width=400 height=300></canvas>

<br>
<span>Start Height:</span>
<input id="iHs" type="range" min=0 max=200 value=10>
<span id="vHs">10</span>

<br>
<span>Middle Height:</span>
<input id="iHm" type="range" min=0 max=200 value=167>
<span id="vHm">167</span>

<br>
<span>End Height:</span>
<input id="iHf" type="range" min=0 max=200 value=30>
<span id="vHf">30</span>

<br>
<span>Middle point:</span>
<input id="iXm" type="range" min=0 max=100 value=50>
<span id="vXm">50</span>

<br>
<span>Offset Y:</span>
<input id="iYo" type="range" min=0 max=100 value=30>
<span id="vYo">30</span>

<br>
<span>Text height:</span>
<input id="iTh" type="range" min=10 max=100 value=60>
<span id="vTh">60</span>

<br>
<span>Text top gap:</span>
<input id="iTo" type="range" min=0 max=30 value=13>
<span id="vTo">13</span>

<br>
<span>Text:</span>
<input id="iText" type="text" value="TEXT ART">
 

1 Still with straight lateral edges.

2 In two dimension only in this case.

Margaret Bloom
  • 41,768
  • 5
  • 78
  • 124
0

See the html5canvas website http://www.html5canvastutorials.com there you find the 'toolkit' i.e. texts, transformations,...

The base of textart is a flat text. If you want this flat text to go in a wavy line you have to use transformations to manipulate the text, i.e. by applicating a Bezier-Curve on the flat text ...

see this jsfiddle http://jsfiddle.net/Makallus/hyyvpp8g/ and this thread How Can I draw a Text Along arc path with HTML 5 Canvas?

html

<table><TR><TH>Bezier Curve</TH><TD>
    <input size="80" type="text" id="curve" name="curve" value="99.2,177.2,130.02,60.0,300.5,276.2,300.7,176.2">
    </TD></TR>
    <TR><TH>Text</TH><TD><input size="80" type="text" id="text" name="text" value="testing 1234567890"></TD></TR>
        <TR><TD colspan=2><div id="canvasDiv"></div></TD></TR>
    </table>

javascript

var first = true;
startIt();


function startIt()
{
    canvasDiv = document.getElementById('canvasDiv');
    canvasDiv.innerHTML = '<canvas id="layer0" width="300" height="300"></canvas>'; //for IE
    canvas = document.getElementById('layer0');
    ctx = canvas.getContext('2d');
    ctx.fillStyle = "black";
    ctx.font = "18px arial black";
    curve = document.getElementById('curve');
    curveText = document.getElementById('text');
    $(curve).keyup(function(e) {changeCurve();});
    $(curveText).keyup(function(e) {changeCurve();});




    if (first)
    {
        changeCurve();
        first = false;
    }

}

function changeCurve()
{
points = curve.value.split(',');  
    if (points.length == 8)
        drawStack();

}

function drawStack()
{
    Ribbon = {maxChar: 50, startX: points[0], startY: points[1], 
              control1X: points[2], control1Y: points[3], 
              control2X: points[4], control2Y: points[5], 
              endX: points[6], endY: points[7]};

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.save();
    ctx.beginPath();

    ctx.moveTo(Ribbon.startX,Ribbon.startY);
    ctx.bezierCurveTo(Ribbon.control1X,Ribbon.control1Y,
                   Ribbon.control2X,Ribbon.control2Y,
                   Ribbon.endX,Ribbon.endY);

    ctx.stroke();
    ctx.restore();

   FillRibbon(curveText.value,Ribbon); 
}

function FillRibbon(text,Ribbon)
{

var textCurve = [];
var ribbon = text.substring(0,Ribbon.maxChar);
var curveSample = 1000;


xDist = 0;
var i = 0;
for (i = 0; i < curveSample; i++)
    {
        a = new bezier2(i/curveSample,Ribbon.startX,Ribbon.startY,Ribbon.control1X,Ribbon.control1Y,Ribbon.control2X,Ribbon.control2Y,Ribbon.endX,Ribbon.endY);
        b = new bezier2((i+1)/curveSample,Ribbon.startX,Ribbon.startY,Ribbon.control1X,Ribbon.control1Y,Ribbon.control2X,Ribbon.control2Y,Ribbon.endX,Ribbon.endY);
        c = new bezier(a,b);
        textCurve.push({bezier: a, curve: c.curve});
    }

letterPadding = ctx.measureText(" ").width / 4; 
w = ribbon.length;
ww = Math.round(ctx.measureText(ribbon).width);


totalPadding = (w-1) * letterPadding;
totalLength = ww + totalPadding;
p = 0;

cDist = textCurve[curveSample-1].curve.cDist;

z = (cDist / 2) - (totalLength / 2);

for (i=0;i<curveSample;i++)
    {
        if (textCurve[i].curve.cDist >= z)
        {
            p = i;
            break;
        }
    }

for (i = 0; i < w ; i++)
    {
        ctx.save();
        ctx.translate(textCurve[p].bezier.point.x,textCurve[p].bezier.point.y);
        ctx.rotate(textCurve[p].curve.rad);
        ctx.fillText(ribbon[i],0,0);
        ctx.restore();

        x1 = ctx.measureText(ribbon[i]).width + letterPadding ;
        x2 = 0; 
        for (j=p;j<curveSample;j++)
        {
        x2 = x2 + textCurve[j].curve.dist;
        if (x2 >= x1)
            {
                p = j;
                break;
            }
        }




    }
} //end FillRibon

function bezier(b1, b2)
{
//Final stage which takes p, p+1 and calculates the rotation, distance on the path and accumulates the total distance
this.rad = Math.atan(b1.point.mY/b1.point.mX);
this.b2 = b2;
this.b1 = b1;
dx = (b2.x - b1.x);
dx2 = (b2.x - b1.x) * (b2.x - b1.x);
this.dist = Math.sqrt( ((b2.x - b1.x) * (b2.x - b1.x)) + ((b2.y - b1.y) * (b2.y - b1.y)) );
xDist = xDist + this.dist;
this.curve = {rad: this.rad, dist: this.dist, cDist: xDist};
}

function bezierT(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY)
{
//calculates the tangent line to a point in the curve; later used to calculate the degrees of rotation at this point.
this.mx = (3*(1-t)*(1-t) * (control1X - startX)) + ((6 * (1-t) * t) * (control2X - control1X)) + (3 * t * t * (endX - control2X));
this.my = (3*(1-t)*(1-t) * (control1Y - startY)) + ((6 * (1-t) * t) * (control2Y - control1Y)) + (3 * t * t * (endY - control2Y));
}

function bezier2(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY)
{
//Quadratic bezier curve plotter
this.Bezier1 = new bezier1(t,startX,startY,control1X,control1Y,control2X,control2Y);
this.Bezier2 = new bezier1(t,control1X,control1Y,control2X,control2Y,endX,endY);
this.x = ((1 - t) * this.Bezier1.x) + (t * this.Bezier2.x);
this.y = ((1 - t) * this.Bezier1.y) + (t * this.Bezier2.y);
this.slope = new bezierT(t,startX, startY,control1X,control1Y,control2X,control2Y,endX,endY);

this.point = {t: t, x: this.x, y: this.y, mX: this.slope.mx, mY: this.slope.my};
}
function bezier1(t,startX, startY,control1X,control1Y,control2X,control2Y)
{
//linear bezier curve plotter; used recursivly in the quadratic bezier curve calculation
this.x = (( 1 - t) * (1 - t) * startX) + (2 * (1 - t) * t * control1X) + (t * t * control2X);
this.y = (( 1 - t) * (1 - t) * startY) + (2 * (1 - t) * t * control1Y) + (t * t * control2Y);

}

See also this http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

Community
  • 1
  • 1
ralf htp
  • 9,149
  • 4
  • 22
  • 34
  • We are implementing text art feature and not text on path. – Ann Mar 02 '16 at 11:48
  • i see Maybe you have to load the text as an image and then use transformation matrices. See this thread http://stackoverflow.com/questions/7937196/image-curving-with-php-or-html5?lq=1 and maybe this thread http://stackoverflow.com/questions/4774172/image-manipulation-and-texture-mapping-using-html5-canvas – ralf htp Mar 02 '16 at 11:52
  • i saw your jsfiddle http://jsfiddle.net/AbdiasSoftware/e8hZy/ and i think you solved the critical point: Adapting the height of a text to a curve or two curves. Now the curves are more sophisticated (i.e. Bezier) however the principle stays the same (adapting height of a text) – ralf htp Mar 02 '16 at 12:21