2

I'm making a drawing app with html5 canvas. User can draw ellipses and select both line color and fill color. (including transparent colors)
When selected color is not transparent, it works fine. But when transparent color is selected and border line width is thick, there are problems.(Q1 and Q2)

This is the image
http://tinypic.com/view.php?pic=28ry4z&s=9#.VoRs7U8jHSg

I'm using drawEllipse() method from below.
the relation of the bezier Curve and ellipse?

Does anyone solve this problems? Any help will be greatly appreciated.

[Q1] When the lineWidth is larger than the ellipse's width, there is a strange blank in the ellipse, and lineWidth is strangely thin. Internet Explorer works fine, but both Firefox and Safari web browsers have this problem. How can I change the blank area to be blue?

[Q2] I'm using transparent colors and I want to draw the ellipse with 2 colors.
(stroke is blue and fill is red) But the stroke color and the fill color are mixed and there is magenta area in the ellipse. How can I draw the ellipse with 2 colors?
(I want to change the magenta area to blue)
One time fill is preferred when possible.

Here is my code

// this method is from
// https://stackoverflow.com/questions/14169234/the-relation-of-the-bezier-curve-and-ellipse
function _drawEllipse(ctx, x, y, w, h) {
  var width_over_2 = w / 2;
  var width_two_thirds = w * 2 / 3;
  var height_over_2 = h / 2;

  ctx.beginPath();
  ctx.moveTo(x, y - height_over_2);
  ctx.bezierCurveTo(x + width_two_thirds, y - height_over_2, x + width_two_thirds, y + height_over_2, x, y + height_over_2);
  ctx.bezierCurveTo(x - width_two_thirds, y + height_over_2, x - width_two_thirds, y - height_over_2, x, y - height_over_2);
  ctx.closePath();
  ctx.stroke();
}

function ellipse_test() {
  var canvas = document.getElementById('sample1');
  var ctx = canvas.getContext('2d');

  var x = 100;
  var y = 100;
  var w = 40;
  var h = 100;
  ctx.lineWidth = 30;
  ctx.fillStyle = "rgba(255,0,0,0.5)";
  ctx.strokeStyle = "rgba(0,0,255,0.5)";
  ctx.globalCompositeOperation = "source-over";

  for (var r = 0; r < 50; r++) {
    _drawEllipse(ctx, x, y, r, r * 2);
    ctx.fill();
    x += 60;

    if (x > 1000) {
      x = 100;
      y += 200;
    }
  }
}

ellipse_test();
<canvas id="sample1" style="border:1px solid blue; background:black;" width="1200" height="800"></canvas>

this is the image on firefox

Community
  • 1
  • 1
Pontarlier
  • 21
  • 5
  • 1
    For Q1 : Drawing an ellipse having a radius of, say, 2, and a lineWidth of 30 would even raise questions for a human, so no wonder the result is strange. ==> ensure you ask for a meaningful drawing. I'd draw your 'eye' with 2 filled ellipse with r / r+30. For Q2 : your are using opacity, so no wonder also that colors are mixed. Use clipping, or draw without opacity on a separate canvas, then with opacity on the main canvas to avoid that. – GameAlchemist Dec 26 '15 at 14:51
  • If you overlay 2 different semi-transparent fills, a 3rd fill will always result. Why are you allowing semi-transparent fills if you don't want to allow the user to blend colors? – markE Dec 29 '15 at 02:03
  • @markE. Thank you for your comment. my question is below. This is current flow 1. draw (openPath to beginPath) 2. stroke 3. fill If one-time draw, one stroke and one fill, is there a way to avoid the colors would be mixed with globalCompositeOperation? (Or the only way to do this is clipping?) – Pontarlier Dec 30 '15 at 01:13
  • Evidently I'm not understanding your question. If you convert semi-transparent color to opaque color (as my answer does) you will not get the unwanted 3rd & 4th "blended" colors. Please clarify your question. :-) – markE Dec 30 '15 at 06:46
  • @markE. Thank you for your comment. I uploaded the image on firefox. Did you see on your browser like this? About Q1, without fill, there is hole when the ellipse is small. Q2: I want to solve this with globalCompositeOperation rather than additional draw(clipping etc.) – Pontarlier Dec 30 '15 at 23:57

1 Answers1

1

Both problems are caused by the fact that multiple strokes/fills of semi-transparent colors over an area will cause that area to become a blend of colors (much like an artist blends multiple colors).

You can resolve both problems by converting semi-transparent colors into opaque colors:

function RGBAtoRGB(r, g, b, a, backgroundR,backgroundG,backgroundB){
  var r3 = Math.round(((1 - a) * backgroundR) + (a * r))
  var g3 = Math.round(((1 - a) * backgroundG) + (a * g))
  var b3 = Math.round(((1 - a) * backgroundB) + (a * b))
  return "rgb("+r3+","+g3+","+b3+")";
} 

// convert 50%-red foreground fill + 100% black background into opaque (=="red-brownish")
ctx.fillStyle = RGBAtoRGB(255,0,0,0.50, 0,0,0,1); // "rgba(255,0,0,0.5)";

// convert 50%-blue foreground stroke + 100% black background into opaque (=="blueish")
ctx.strokeStyle = RGBAtoRGB(0,0,255,0.50, 0,0,0,1); // "rgba(0,0,255,0.5)";

enter image description here

Example code refactored to use opaque fills & strokes:

ellipse_test();

// this method is from
// http://stackoverflow.com/questions/14169234/the-relation-of-the-bezier-curve-and-ellipse
function _drawEllipse(ctx, x, y, w, h) {
  var width_over_2 = w / 2;
  var width_two_thirds = w * 2 / 3;
  var height_over_2 = h / 2;

  ctx.beginPath();
  ctx.moveTo(x, y - height_over_2);
  ctx.bezierCurveTo(x + width_two_thirds, y - height_over_2, x + width_two_thirds, y + height_over_2, x, y + height_over_2);
  ctx.bezierCurveTo(x - width_two_thirds, y + height_over_2, x - width_two_thirds, y - height_over_2, x, y - height_over_2);
  ctx.closePath();
}

function ellipse_test() {
  var canvas = document.getElementById('sample1');
  var ctx = canvas.getContext('2d');

  var x = 100;
  var y = 100;
  var w = 40;
  var h = 100;
  ctx.lineWidth = 30;

  ctx.fillStyle = 'black';
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = RGBAtoRGB(255, 0, 0, 0.50, 0, 0, 0, 1); // "rgba(255,0,0,0.5)";
  ctx.strokeStyle = RGBAtoRGB(0, 0, 255, 0.50, 0, 0, 0, 1); // "rgba(0,0,255,0.5)";


  ctx.globalCompositeOperation = "source-over";

  for (var r = 0; r < 50; r++) {
    _drawEllipse(ctx, x, y, r, r * 2);
    ctx.stroke();
    ctx.fill();
    x += 60;

    if (x > 1000) {
      x = 100;
      y += 200;
    }
  }
}


function RGBAtoRGB(r, g, b, a, backgroundR, backgroundG, backgroundB) {
  var r3 = Math.round(((1 - a) * backgroundR) + (a * r))
  var g3 = Math.round(((1 - a) * backgroundG) + (a * g))
  var b3 = Math.round(((1 - a) * backgroundB) + (a * b))
  return "rgb(" + r3 + "," + g3 + "," + b3 + ")";
}
body {
  background-color: ivory;
}
#canvas {
  border: 1px solid red;
  background-color=black;
}
<canvas id="sample1" width=1200 height=800></canvas>

Overlapping

...And obviously if you draw your ellipses very close together they will eventually overlap. That's what's causing your Q1-line thinning.

markE
  • 102,905
  • 11
  • 164
  • 176
  • Thank you for your answer. Your answer was very informative. But I'm sorry that my information was not enough. I'm making a drawing app and it can select both line color and fill color. (including transparent colors) When selected color is not transparent, it works fine. But when transparent color is selected, there are problems.(Q1 and Q2) And Internet Explorer doesn't have Q1's problem. But both Firefox and Safari have Q1's problem. – Pontarlier Dec 29 '15 at 01:58