Draw sketch line, how? lazarus / 39-Graphics ---------------------------------------------------------------------- x2nie · 2016-07-11 16:38 Hi gurus, Is there any idea/code of drawing sketch line in TCanvas? I mean, it's not exactly similar to known straight line, but similar to those call of  MoveTo(x,y), LineTo(x,y). Additionally, it should be render some randomness along the line. (see attachments) I think it would be nice to have such that procedures, especially on drawing mockup / UI sketch. Currently, I have no idea to implement this. ---------------------------------------------------------------------- lainz · 2016-07-11 16:51 I think you can have a bitmap that can be repeated like a pattern and draw it several times until you fill the line space horizontally or vertically. Or use bgrabitmap and draw with an array of points with the randomness + 5 and -5 píxels maybe less or more. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);var  p: array of TPointF;  i: integer;  x, y: integer;begin   SetLength(p, 100);   y := 10;  x := 0;   for i:= 0 to Length(p)-1 do  begin    p[i] := PointF(x, RandomRange(y, y + 5));    x := x + 10;  end;   Bitmap.DrawPolyLineAntialias(p, BGRABlack, 4, False);end; ---------------------------------------------------------------------- x2nie · 2016-07-11 18:02 thanks you @lainz, would you like to show us the result/screenshot? Now, I have understanding that it should use something like "bezier Curve" to smooth the randomness. well, anyway I just found some raw code of drawing "imprecisely line" : http://jakevdp.github.io/blog/2012/10/07/xkcd-style-plots-in- matplotlib/ http://jakevdp.github.io/blog/2013/07/10/XKCD-plots-in-matplotlib/ http://webhome.cs.uvic.ca/~blob/papers/hla.pdf http://stevehanov.ca/blog/index.php?id=33 https://github.com/amolenaar/gaphas/blob/master/gaphas/freehand.py http://www.cs.usask.ca/~mould/papers/crayon.pdf http://www.cs.usask.ca/~mould/papers/crayon-cgf.pdf none of them were written in pascal (except lainz's code). ---------------------------------------------------------------------- lainz · 2016-07-11 18:18 You can adjust the size and lenght of the line to get different results The second is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     p[i] := PointF(x, RandomRange(y, y + 3));    x := x + 30;  end;   Bitmap.DrawPolyLineAntialias(p, BGRABlack, 2, False); ---------------------------------------------------------------------- Graeme · 2016-07-11 22:48 Quote from: x2nie on July 11, 2016, 08:02:18 pmwell, anyway I just found some raw code of drawing "imprecisely line" : http://jakevdp.github.io/blog/2012/10/07/xkcd-style-plots-in- matplotlib/ Cool - I really like that one. ---------------------------------------------------------------------- circular · 2016-07-12 01:32 You can try using TBGRAPath from unit BGRAPath. Add the elements you want to draw then call ToPoints to retrieve the list of points. Then transform those points. With IsEmptyPointF you can check that the point is a separator between shapes. With VectLen you can get the length of a vector in a TPointF (the difference of two TPointF v and w is simply v - w). So you can compute unit vectors and normal vectors that you can use to add jitter. ---------------------------------------------------------------------- Graeme · 2016-07-12 08:36 Quote from: x2nie on July 11, 2016, 08:02:18 pmhttp://jakevdp.github.io/blog/2013/07/10/XKCD-plots-in-matplotlib/ Last night I quickly converted this one from C & Cairo to Object Pascal & AggPas, but I'm afraid the conversion is not 100%. The more lines I draw, the more "incorrect" the hand-drawn lines look. It uses Cubic Bezier Curves with two control points - well, that is what I use in AggPas, and assume that is what the Cairo call used too. I didn't double check the Cairo documentation. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure crazyLine(const ctx: TAgg2D; fromX, fromY, toX, toY: double);const  DEBUG: boolean = True;var    // The idea is to draw a curve, setting two control points at random    // close to each side of the line. The longer the line, the sloppier it's drawn.    control1x, control1y: double;    control2x, control2y: double;    length: double;    offset: double;    t1X, t1Y, t2X, t2Y: double;    r: double;begin    // Crazyline. By Steve Hanov, 2008    // Released to the public domain.  ctx.ResetPath;    ctx.LineColor(0, 0, 0);     // calculate the length of the line.    length := sqrt( (toX-fromX)*(toX-fromX) + (toY- fromY)*(toY-fromY));     // This offset determines how sloppy the line is drawn. It depends on the    // length, but maxes out at 20.    offset := length / 20;    if (offset > 20) then      offset := 20;   // Overshoot the destination a little, as one might if drawing with a pen.    toX += random * offset / 4;    toY += random * offset / 4;   t1X := fromX;    t1Y := fromY;    t2X := toX;    t2Y := toY;     // t1 and t2 are coordinates of a line shifted under or to the right of    // our original.    t1X += offset;    t2X += offset;    t1Y += offset;    t2Y += offset;     // create a control point at random along our shifted line.    r := random;    control1X := t1Y + r * (t2X-t1X);    control1Y := t1Y + r * (t2Y-t1Y);     // now make t1 and t2 the coordinates of our line shifted above    // and to the left of the original.    t1X := fromX - offset;    t2X := toX - offset;    t1Y := fromY - offset;    t2Y := toY - offset;     // create a second control point at random along the shifted line.    r := random;    control2X := t1X + r * (t2X-t1X);    control2Y := t1Y + r * (t2Y-t1Y);    // better, but still not correct//    control2X := t2X - r * (t2X-t1X);//    control2Y := t2Y - r * (t2Y-t1Y);//    writeln(Format('(%.2f, %.2f)   (%.2f, %.2f)', [control1X, control1Y, control2X, control2Y]));     // draw the line!    ctx.MoveTo(fromX, fromY);    ctx.CubicCurveTo(control1X, control1Y, control2X, control2Y, toX, toY);    ctx.DrawPath;     if DEBUG then    begin    ctx.ResetPath;      ctx.NoFill;      ctx.LineColor(80, 0, 0);      ctx.Ellipse(control1X, control1Y, 3, 3);      ctx.LineColor(0, 80, 0);      ctx.Ellipse(control2X, control2Y, 3, 3);    end; end;  I was simply curious to see the result. I also drew the two control points (red & green circles) used to see where the line drawing goes wrong. Time permitting, I'll take another look at this. First impressions, Lainz's code produces nicer results. I do still want to convert the "imprecisely line" matplot patch too - that gave very nice results. ---------------------------------------------------------------------- x2nie · 2016-07-12 10:07 Wow! So, it involves cubic curve.. ?.   I guessed, but I don't understand cairo either. @Graeme, just FYI, CrazyLine has been advanced here: https://github.com/amolenaar/gaphas/blob/master/gaphas/freehand.py Which sloppiness can be styled as 4 main possible values: Code: Python  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i          """        Create context with given sloppiness. Range [0..2.0] gives acceptable        results.        * Draftsman: 0.0        * Artist: 0.25        * Cartoonist: 0.5        * Child: 1.0        * Drunk: 2.0      """ Q: What is sloppiness? Answer: Huge parameter variations. The factor by which each parameter can vary, keeping the system behavior fixed. So, I think the sloppiness is the key of style/theme of how lines are drawn for uniforming taste/art. Sadly there are no screenshot of the  code (I am curious how "Drunk" people draw a line?) ---------------------------------------------------------------------- x2nie · 2016-07-12 10:18 Quote from: Graeme on July 12, 2016, 10:36:51 ambut I'm afraid the conversion is not 100%. The more lines I draw, the more "incorrect" the hand-drawn lines look. your red circle is 1st control, right? and the green is 2nd. But they seem as are reversed for my eyes (according to screenshot of original CrazyLine). : Red point should be the second control, and the green point should be the first. ? ---------------------------------------------------------------------- circular · 2016-07-12 11:36 Yes they are reversed, that's what gives a sort of angle, otherwise they would be rounded. ---------------------------------------------------------------------- Graeme · 2016-07-12 13:00 Quote from: x2nie on July 12, 2016, 12:07:24 pm@Graeme, just FYI, CrazyLine has been advanced here: https://github.com/amolenaar/gaphas/blob/master/gaphas/freehand.py Interesting... I like the idea of the sloppiness factor. QuoteSadly there are no screenshot of the  code (I am curious how "Drunk" people draw a line?)  Here are screenshots... URL was found in the source code. Scroll down to Drawing lines and curves. http://stevehanov.ca/blog/index.php?id=93 ---------------------------------------------------------------------- Graeme · 2016-07-12 13:34 Quote from: x2nie on July 12, 2016, 12:18:23 pmyour red circle is 1st control, right? and the green is 2nd. Yes. QuoteBut they seem as are reversed for my eyes (according to screenshot of original CrazyLine). I converted my code to the improved CrazyLine() implementation and it still resulted in incorrect line drawing. At closer inspection, I made some silly mistakes with the variable names (copy & paste is evil ). Anyway once I fixed my errors, the line drawing started to work [screenshot2 & screenshot3]. I've also drawn the control point lines to understand the bezier curve better. ---------------------------------------------------------------------- Graeme · 2016-07-12 13:44 Quote from: Graeme on July 12, 2016, 03:34:01 pmAnyway once I fixed my errors, the line drawing started to work Here is a more complete example showing the various "sloppiness" settings. The rectangle was drawn with "child" sloppiness. Here is the final code for CrazyLine(). Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure crazyLine(const ctx: TAgg2D; fromX, fromY, toX, toY: double; sloppiness: double = 0.5);const  DEBUG: boolean = false;var    // The idea is to draw a curve, setting two control points at random    // close to each side of the line. The longer the line, the sloppier it's drawn.    control1x, control1y: double;    control2x, control2y: double;    length: double;    offset: double;    t1X, t1Y, t2X, t2Y: double;    r: double;begin    // Crazyline. By Steve Hanov, 2008    // Released to the public domain.    ctx.ResetPath;    ctx.NoFill;    ctx.LineColor(0, 0, 0);     // calculate the length of the line.    length := sqrt( (toX-fromX)*(toX-fromX) + (toY-fromY)*(toY-fromY));   // This offset determines how sloppy the line is drawn. It depends on the    // length, but maxes out at 20.    offset := length / 10 * sloppiness;    if (offset > 20) then      offset := 20;     // Overshoot the destination a little, as one might if drawing with a pen.    toX := toX + sloppiness * random * offset/4;    toY := toY + sloppiness * random * offset / 4;     // t1 and t2 are coordinates of a line shifted under or to the right of    // our original.    t1X := fromX + offset;    t1Y := fromY + offset;    t2X := toX + offset;    t2Y := toY + offset;     // create a control point at random along our shifted line.    r := random;    control1X := t1X + r * (t2X-t1X);    control1Y := t1Y + r * (t2Y-t1Y);     // now make t1 and t2 the coordinates of our line shifted above    // and to the left of the original.    t1X := fromX - offset;    t2X := toX - offset;    t1Y := fromY - offset;    t2Y := toY - offset;     // create a second control point at random along the shifted line.    r := random;    control2X := t1X + r * (t2X-t1X);    control2Y := t1Y + r * (t2Y-t1Y);     // draw the line!    ctx.MoveTo(fromX, fromY);    ctx.CubicCurveTo(control1X, control1Y, control2X, control2Y, toX, toY);    ctx.DrawPath;     if DEBUG then    begin      ctx.ResetPath;      ctx.NoFill;       // red - control point 1      ctx.LineColor($FF, 0, 0);      ctx.Ellipse(control1X, control1Y, 3, 3);      ctx.LineColor($E6, $E6, $EF, 150);      ctx.Line(fromX, fromY, control1X, control1Y);       // green - control point 2      ctx.LineColor(0, $FF, 0);      ctx.Ellipse(control2X, control2Y, 3, 3);      ctx.LineColor($E6, $E6, $EF, 150);      ctx.Line(toX, toY, control2X, control2Y);    end;end; ---------------------------------------------------------------------- circular · 2016-07-12 18:46 Quite nice