espruino / Espruino

The Espruino JavaScript interpreter - Official Repo
http://www.espruino.com/
Other
2.77k stars 745 forks source link

Enhancement for Graphics #1702

Closed MaBecker closed 4 years ago

MaBecker commented 4 years ago

This is just a collection of function that came in my mind when working with the excellent library.

For a better readability, will try to write one comment for each enhancement

Updated 05/29/2020

MaBecker commented 4 years ago
MaBecker commented 4 years ago
MaBecker commented 4 years ago

Use a image and some javascript for rotation, like in this sample

http://forum.espruino.com/conversations/344607/#comment15140533

Update: 03/13/2020

MaBecker commented 4 years ago
setInterval(function() {
  g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2});
},100);

this would nice to have for poly functions.

gfwilliams commented 4 years ago

https://banglejs.com/reference#l_Graphics_drawImage

Those definitely seem like good things to add.

I was considering maybe moving to a better fillPoly (that could do irregular polys) then handling drawLine using that. The real bonus of a line width is you could use the HersheyText line fonts, which scale really nicely

MaBecker commented 4 years ago

At the moment I use this workaround, which allows to scale and translate a array with pairs of points..

function translate(x, y, p) {
    p.forEach((e, i) => {
        p[i] += (i % 2) ? y : x;
    });
    return p;
}

function scale(x, y, p) {
    p.forEach((e, i) => {
        p[i] *= (i % 2) ? y : x;
    });
    return p;
}

var plStop = [0,0,20,0,20,20,0,20];
//g.fillPoly(translate(40,170,scale(2,2,plStop)));
g.fillPoly(translate(40,170,scale(2,2,plStop.slice())));
var plPlay = [0,0,20,10,0,20];
//g.fillPoly(translate(160,170,scale(2,2,plPlay)));
g.fillPoly(translate(160,170,scale(2,2,plPlay.slice())));

Updated: Pass a copy of the array using .slice(), otherwise the original array is updated :-(

MaBecker commented 4 years ago
gfwilliams commented 4 years ago

allObjects has a UI library that looks really promising which just needs adding to the EspruinoDocs repo with all his documentation - I just haven't had the time so far

MaBecker commented 4 years ago

Yes this UI is very cool 😎

MaBecker commented 4 years ago

Replace optimized Ellipse with standard Bresenham algorithm to remove the spikes.

Eye draw test on Bangle.js

Source

g.clear();
g.setColor(1,1,1).fillEllipse(40,60,120+80,180);
g.setColor(0,0,1).fillCircle(120,120,50);
g.setColor(0,0,0).fillCircle(120,120,20);
g.setColor(1,1,1).fillCircle(112,112,5);

Result:

IMG_9591

MaBecker commented 4 years ago

Hershey Fonts

Started with diving into the hershey world. This is extremely cool. http://coopertype.org/event/the_hershey_fonts https://emergent.unpythonic.net/software/hershey http://paulbourke.net/dataformats/hershey/

And now starting to code in javascript to figure out how simple this can be to implement into Graphics library.

implement:

Add a sample

// 1
p1 = [15,22,[ 6,5, 8,4, 11,1, 11,22 ]];
// 2
p2 = [ 15,22, [4,6, 4,5, 5,3, 6,2, 8,1, 12,1, 14,2, 15,3, 16,5, 16,7, 15,9, 13,12, 3,22, 17,22 ]];

g.clear();

var translate = (tx, ty, p) => p.map((v, i)=> v + ((i&1) ? ty : tx));
var scale = (sx, sy, p) => p.map((v, i) => (v * ((i&1) ? sy : sx)+0.5) |0   );

function drawLine(x1,y1,x2,y2,thickness){
  var p = [];
  var angle = Math.atan2(y2-y1,x2-x1);
  cosP = Math.cos(angle+Math.PI/2);
  cosM = Math.cos(angle-Math.PI/2);
  sinP = Math.sin(angle+Math.PI/2);
  sinM = Math.sin(angle-Math.PI/2);
  p[0] = (x1 + thickness*cosP +0.5)|0;
  p[1] = (y1 + thickness*sinP +0.5)|0;
  p[2] = (x1 + thickness*cosM +0.5)|0;
  p[3] = (y1 + thickness*sinM +0.5)|0;
  p[4] = (x2 + thickness*cosM +0.5)|0;
  p[5] = (y2 + thickness*sinM +0.5)|0;
  p[6] = (x2 + thickness*cosP +0.5)|0;
  p[7] = (y2 + thickness*sinP +0.5)|0;
  g.fillPoly(p,true);
}

var p = translate(75,75,scale(3,3,p2[2]));
var t = 3;

g.clear();

for(i=0; i < p.length-2;i += 2){
  console.log(p[i+0],p[i+1],p[i+2],p[i+3]);
  drawLine(p[i+0],p[i+1],p[i+2],p[i+3],t);
  g.fillCircle(p[i+0],p[i+1],t);
  g.fillCircle(p[i+2],p[i+3],t);
}

Bildschirmfoto 2019-12-10 um 18 23 26

adding circles makes it look smoother.

Edit:

compare with existing vector font

Bildschirmfoto 2019-12-11 um 07 05 37

gfwilliams commented 4 years ago

Do you have a PR for the new ellipse? ;)

MaBecker commented 4 years ago

Yep, just added pr #1720

MaBecker commented 4 years ago

Would like to implement thickness for drawLine. Can you please advise if and how it should be implemented.

gfwilliams commented 4 years ago

Sounds like a good plan. I'd store lineThickness the same way we do fontAlign/etc in graphics, then check in drawPoly and run a separate bit of code. It should be ifdef'd with SAVE_ON_FLASH though.

The easy way to do it would be to draw each line segment as a poly as you've done, but I think that's going to be too slow if we want to replace Vector with Hershey fonts (which I guess is the main goal?) as there'd be loads of overdraw, and really we want to work on the whole polyline

The actual integration with graphics would take a while and would be a waste of your time, so if you could come up with an algorithm that would turn a polyline into a filled poly then I could integrate it. Even doing the algorithm in JS would work.

Something line this - you'd need to generate the red points:

image

In your code above i notice you're taking the angle and the using sin and cos, but that's very slow and you can actually just use the difference between coordinates:

dx = x2-x1;
dy = y2-y1;
d = sqrt(dx*dx + dy*dy);
dx = dx*lineWidth/d;
dy = dy*lineWidth/d;
// then [dy,-dx] is 90 degrees, [-dy,dx] is the other way
// [dx,dy] is in-line, so [(dx+dy)/2, (dy-dx)/2] 

You can even compare dx/dy with the next dx/dy using a cross product in order to work out which way the line bends (and how much) to figure out if points need adding or removing.

gfwilliams commented 4 years ago

... only saw your update with the Hershey font you printed while writing this. It looks great - it's hard to see how anyone would complain if the font changed :)

MaBecker commented 4 years ago

Great, thanks for you guiding informations.

MaBecker commented 4 years ago

Just a short update on testing hershey font using a C-Style Coordinate array for the SIMPLEX character set instead of decoding jhf.

Mirrored and move chars to have same height and baseline. Also adjusted some points for a nice shape.

Started with numbers 0 - 9, scaling 1.5

Bildschirmfoto 2019-12-12 um 18 10 15

/*
 var hfl =  [
        width, heigth, numLines,
        [ fist line x,y ],
        ...
        [ last line x,y ]
    ];
*/
  ......
   // /* 1 Ascii 49 */
    [
        6, 22, 1,
        [0, 5, 2, 3, 5, 0, 5, 21]

    ],

function to setLineWith

function setLineWidth(x1, y1, x2, y2, lw) {
    var dx = x2 - x1;
    var dy = y2 - y1;
    var d = Math.sqrt(dx * dx + dy * dy);
    dx = dx * lw / d;
    dy = dy * lw / d;

    return [
        // rounding
        x1 - (dx + dy) / 2, y1 - (dy - dx) / 2,
        x1 - dx, y1 -dy,
        x1 + (dy - dx) / 2, y1 - (dx + dy) / 2,

        x1 + dy, y1 - dx,
        x2 + dy, y2 - dx,

        // rounding
        x2 + (dx + dy) / 2, y2 + (dy - dx) / 2, 
        x2 + dx, y2 + dy,
        x2 - (dy - dx) / 2, y2 + (dx + dy) / 2, 

        x2 - dy, y2 + dx,
        x1 - dy, y1 + dx
    ];
}

g.fillPoly(setLineWidth(20,20,100,200,5));

Bildschirmfoto 2019-12-12 um 18 54 48

Not sure about those six rounding lines.

MaBecker commented 4 years ago

First result with adding line width to poly segments

IMG_9606

Fontsize: 66px

MaBecker commented 4 years ago

working with scaling, thickness and kerning every one can create his individual look

hf = { sx: 2.5, sy :5, t: 5, k: 5 }; sx : font width sy : font height, default 22px t: thickness in px k : kerning in px

IMG_9608

MaBecker commented 4 years ago

Or as small font with hf = { sx: 1, sy :1, t: 1, k: 2 };

IMG_9617

MaBecker commented 4 years ago

Maybe add a function like this to handle the style.

 g.setFontHershey({width: in_px, heigth : in_px, bold : in_px, kerning : in_px});
MaBecker commented 4 years ago

Before starting to convert hershey font files to header file, some advise for output format would be helpful to create eg hershey_romans_font.h

static const unsigned char hersheyFontPolys[] IN_FLASH_MEMORY = {
 // Character code 32
...
 // Character code 255
 }
gfwilliams commented 4 years ago

Nice! About the rounding, try changing x1 - (dx + dy) / 2, y1 - (dy - dx) / 2, to x1 - (dx + dy) * 0.71, y1 - (dy - dx) * 0.71, - it should make it more rounded :)

gfwilliams commented 4 years ago

In other fonts I've had one array for character data, one array for # of points. It means you have to iterate through to find each character, but it immediately shaves 100 bytes off the structure because you can use 8 bits rather than 16 for the length.

MaBecker commented 4 years ago

In other fonts I've had one array for character data, one array for # of points

Yes, seem to be the easiest way to implement it, like you did for the vector font.

MaBecker commented 4 years ago

Got a version javascript version running

https://gist.github.com/MaBecker/89a8ec3314f456ccfe397593783ec8b4

What about spacing between chars?

MaBecker commented 4 years ago

Decided to use different factor for drawing the rounded edges.

Bildschirmfoto 2019-12-16 um 17 14 28

Is there a solution to handle yellow marked section, because of the slope the width is to small

gfwilliams commented 4 years ago

Are you actually doing what's suggested in https://github.com/espruino/Espruino/issues/1702#issuecomment-564541847 ? Looks like you might just be producing one polygon per line segment?

Doing one big poly would go a long way towards fixing those glitches, but I had considered maybe making the internal poly fill algorithm take coordinates that were 16x bigger (so 16 = 1 pixel, 32 = 2, etc) would help with issues like this

MaBecker commented 4 years ago

Looks like you might just be producing one polygon per line segment?

Yes, just started working on this :)

making the internal poly fill algorithm take coordinates that were 16x bigger (so 16 = 1 pixel, 32 = 2, etc) would help with issues like this

Good point, will include it.

MaBecker commented 4 years ago

Just added this pr https://github.com/espruino/Espruino/pull/1757 for g.quadraticBezier()

MaBecker commented 4 years ago

http://forum.espruino.com/conversations/344773/

Would be easy to add to firmware.

gfwilliams commented 4 years ago

Here's my attempt at Hershey rendering - creating one big poly rather than one per line:

https://www.espruino.com/ide/emulator.html?gist=663f7d6e280e33511881a3dfff2ee30f&upload

Still a few edge cases, but reasonably quick and significantly less overdraw.

hershey8

MaBecker commented 4 years ago

Wow, just tested, very nice!

Have you thought about about chars > 127 and < 256 ?

Edit: Even still quick enough on a Bangle.js

gfwilliams commented 4 years ago

It'll be much quicker when it's converted to C too :)

Potentially we could add them, yes. Euros and degrees at least.

One of the reasons for doing this was to reduce flash usage though so I don't want to bump it up too much!

I'm not 100% on the characters but it strikes me that for many of them we could maybe hardcode it? So Ü = U + umlaut ?

gfwilliams commented 4 years ago

Obviously there's still the fillPoly issue. You wouldn't think it'd be so hard to get a reliable polygon fill algorithm!

MaBecker commented 4 years ago

I'm not 100% on the characters but it strikes me that for many of them we could maybe hardcode it? So Ü = U + umlaut ?

Yes they are called Umlaute äöüÄÖÜ plus €ß°

ü = u + umlaut Ü = U + same as umlaut, just on a different position €: parts from O plus two lines ß: parts of 3 plus a line

I was spending many hours scanning other graphic libs for a smart, short, quick and working algorithm, without success. fillPoly is not as simple as it looks.

MaBecker commented 4 years ago

I like the look and feel of the chars with debug flag!

gfwilliams commented 4 years ago

Ok, change of plan again. A custom-made vector font seems preferable: https://github.com/espruino/Espruino/issues/1824

MaBecker commented 4 years ago

time to close this ;-)