Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it possible to crop images of a segment by its borders? #95

Open
MaliugaSergiy opened this issue May 6, 2020 · 3 comments
Open

Is it possible to crop images of a segment by its borders? #95

MaliugaSergiy opened this issue May 6, 2020 · 3 comments
Assignees
Labels

Comments

@MaliugaSergiy
Copy link

I want behaviour like in css - overflow: hidden

is it possible?

@MaliugaSergiy
Copy link
Author

MaliugaSergiy commented May 6, 2020

also, is it possible to manipulate image as background in css - for example - set positioning?

@zarocknz zarocknz self-assigned this May 6, 2020
@zarocknz
Copy link
Owner

zarocknz commented May 6, 2020

Hi. Unfortunately is not possible to use CSS on HTML canvas. Drawing on canvas is like using MS paint where you can only use basic lines, shapes, and text.

You will need to create the image(s) exactly as they need to appear in the segments with transparency outside the borders of the segments.

@josephrocca
Copy link

josephrocca commented Feb 15, 2021

I also wanted to do this so I put together some code to achieve it. Sharing here in case it's useful to others:

  async function getArcClippedCanvas(imageUrl, radius, arcSizeDeg) {   
    let arcSizeRad = (arcSizeDeg/360)*2*Math.PI;
    
    // derive required width and height of canvas from radius and arc size
    let width;
    if(arcSizeDeg >= 180) {
      width = radius*2;
    } else {
      width = radius*Math.sin(arcSizeRad/2)*2;
    }
    
    let height;
    if(arcSizeDeg <= 180) {
      height = radius;
    } else {
      height = radius + radius*Math.sin( (arcSizeRad-Math.PI)/2 );
    }
    
    let arcCenterX = width/2;
    let arcCenterY = radius; // remember, y axis starts from top of canvas
     
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    
    canvas.width = width;
    canvas.height = height;
    
    let img = new Image();
    await new Promise(resolve => {
      img.onload = resolve;
      img.src = imageUrl;
    });
    
    let centerAngle = -Math.PI/2;
    
    ctx.beginPath();
    ctx.moveTo(arcCenterX, arcCenterY); 
    ctx.arc(arcCenterX, arcCenterY, radius, centerAngle - (arcSizeDeg/2)*2*Math.PI/360, centerAngle + (arcSizeDeg/2)*2*Math.PI/360);
    ctx.clip();
    
    // we want to "cover" the canvas with the image without changing the image's aspect ratio
    drawImageToCanvasContained(ctx, img, 0, 0, canvas.width, canvas.height);
    
    return canvas;
  }


  function drawImageToCanvasContained(ctx, img, x, y, w, h, offsetX, offsetY) {
    // By Ken Fyrstenberg Nilsen: https://stackoverflow.com/a/21961894/11950764
    if(arguments.length === 2) {
      x = y = 0;
      w = ctx.canvas.width;
      h = ctx.canvas.height;
    }

    // default offset is center
    offsetX = typeof offsetX === "number" ? offsetX : 0.5;
    offsetY = typeof offsetY === "number" ? offsetY : 0.5;

    // keep bounds [0.0, 1.0]
    if(offsetX < 0) offsetX = 0;
    if(offsetY < 0) offsetY = 0;
    if(offsetX > 1) offsetX = 1;
    if(offsetY > 1) offsetY = 1;

    let iw = img.width;
    let ih = img.height;
    let r = Math.min(w / iw, h / ih);
    let nw = iw * r;   // new prop. width
    let nh = ih * r;   // new prop. height
    let cx, cy, cw, ch, ar = 1;

    // decide which gap to fill    
    if(nw < w) ar = w / nw;                             
    if(Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh;  // updated
    nw *= ar;
    nh *= ar;

    // calc source rectangle
    cw = iw / (nw / w);
    ch = ih / (nh / h);

    cx = (iw - cw) * offsetX;
    cy = (ih - ch) * offsetY;

    // make sure source rectangle is valid
    if(cx < 0) cx = 0;
    if(cy < 0) cy = 0;
    if(cw > iw) cw = iw;
    if(ch > ih) ch = ih;

    // fill image in dest. rectangle
    ctx.drawImage(img, cx, cy, cw, ch,  x, y, w, h);
  }
  

And here's how I'm using getArcClippedCanvas(...) to manually create the imgData property of each segment that Winwheel creates behind the scenes:

      let segments = [ ... ];
      ...
      let segmentSizeSum = segments.reduce((a,v) => a + (v.size || 0), 0);
      let unsizedSegments = segments.reduce((a,v) => a + (v.size === undefined ? 1 : 0), 0);
      let segmentSizeRemainder = 360 - segmentSizeSum;
      for(let segment of segments) {
        let segmentSize = segment.size !== undefined ? segment.size : segmentSizeRemainder/unsizedSegments;
        segment.imgData = await window.getArcClippedCanvas(segment.image, wheelDiameter/2, segmentSize);
        delete segment.image;
      }
      ...

It "contains" and centers the image within the "slice". I haven't tested this super thoroughly, but it seems to be working okay so far.

Edit: Ah, I've just noticed that if a segment is larger than 180 degrees then the segment's image doesn't render in the correct place. I can see why it's happening, but I've not yet looked into Winwheel's internals to see how hard this is to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants