Out of curiosity I wanted to try to set the lineWidth < 1 because the 1px lines looked fat even with my resolution set correctly. Unsurprisingly it doesn't work but there is this weird behaviour on Chrome and Firefox (not tested elsewhere):
On the left is with lineWidth = 1, center is lineWidth = 0.5 and right is lineWidth = 0.1
They were generated with this code:
ctx.lineWidth = 0.1;
lis.each(function(i) {
sx = $(this).offset().left;
sy = $(this).offset().top;
ex = sx - (20 * (6-i));
ey = wh - 80 - (20 * (i + 1));
eey = ey - (20 * i);
// Horizontal
ctx.moveTo(sx,sy+7);
ctx.lineTo(ex, sy+7);
ctx.stroke();
// Vertical
ctx.moveTo(ex,sy+7);
ctx.lineTo(ex, ey);
ctx.stroke();
// Horizontal
ctx.moveTo(ex,ey);
ctx.lineTo(ww - bg_img_width + 100, eey);
ctx.stroke();
});
They are printed in the order their children appear so it starts with Alpha each time and ends with Epsilon.
Can anyone explain why the lines get thiner as the loop progresses when 0 < lineWidth < 1? Is this intended? Can it be used for cool stuff?
First thing to remind is that points in a canvas are centered on (0.5;0.5) , so ,to draw a clean 1px wide line, one has to draw at integer coordinates + (0.5, 0.5).
Then to emulate thickness, the renderer will play on opacity : a 0.5 line will be rendered less opacity to make it look 'weaker'.
Notice that this is the way the antialiasing works also : it diffuses the point on several neighbor points with a lower weight to simulate the thickness of the line.
You can play with the below fiddle were i compare several ways to draw a triangle.
I think we have same visual effect than with lineWidth=0.5 when we draw with 0.8 opacity for instance.
The two first lines are here to show the the difference in render when using integer coordinates vs when using integer + 0.5 coordinates. We see that when using integer coordinates, the lines overlap on two pixels and seems wider.
We see also that antialiasing is not that good since the diagonal always seems thicker (same on Chrome or Firefox here).
http://jsbin.com/voqubexu/1/edit?js,output
Related
In SkiaSharp I can nicely fill the space between two curves by using SKPathFillType.EvenOdd. Below I show a simplified excerpt from the code.
My question is how can I give a certain pattern to this filled area between the curves ? Here I can only fill it with a color and give it a transparency. I'm interested in applying a pattern, such as hatch or dots.
Thank you for any support.
Greetings,
Sorin
SKPath path = new SKPath();
path.FillType = SKPathFillType.EvenOdd;
// start the first curve
path.MoveTo(....);
path.LineTo(....); // draw the curve and close it
....
path.AddCircle(....); // add a second curve as a circle
SKPaint paint = new SKPaint(new SKFont(SKTypeface.Default)) {
IsAntialias = true,
Style = SKPaintStyle.Fill,
Color = SKColors.Blue.WithAlpha((byte)(0xFF * (1 - 0.5))),
StrokeWidth = 1
};
canvas.DrawPath(path, paint);
I've managed to fix this with a trick.
First of all, I do all I wrote above, i.e.
canvas.DrawPath(path, paint)
.... will draw a filled area between the two curves, with a certain transparency.
On top of that (literally), I draw another pattern:
var hatch = new SKPath();
hatch.AddCircle(0, 0, 1);
var hatchPaint = new SKPaint {
PathEffect = SKPathEffect.Create2DPath(SKMatrix.MakeScale(7, 7), hatch),
Color = SKColors.RosyBrown,
Style = SKPaintStyle.Stroke,
StrokeWidth = 3
};
And again:
canvas.DrawPath(path, hatchPaint);
This draws a nice hatch pattern on top of the filled area between the curves.
Note: the size of the pattern is essential - here AddCircle(0, 0, 1), where the circle ray is 1 pixel. If you have a larger one, the hatch pattern will spill out the filled area, which is not what you want. To me this looks like a bug in SKIA.
Im having issue with clearRect, i have an image u can move up and down and which follow the angle where the mousse is but in some frames of the animation the clearRect let a small edge of the previous image state ( 'this' reference to the image and 'ctx' is the 2d context, 'this.clear()' is called each frame before redrawing the image at the new coordinates )
this.clear = function(){
game.ctx.save();
game.ctx.translate(this.x+this.width/2, this.y+this.height/2);//i translate to the old image center
game.ctx.rotate(this.angle);//i rotate the context to the good angle
game.ctx.clearRect(this.width/-2, this.height/-2, this.width, this.height);//i clear the old image
game.ctx.restore();
};
if i replace the clearRect line by
game.ctx.clearRect(this.width/-2-1, this.height/-2-1, this.width+2, this.height+2);
it works but its not the logical way
The problem is that you are only clearing at position half the width/height, not position minus half the width/height.
Regarding anti-aliasing: when you do a rotation there will be anti-aliased pixels regardless of the original position being integer values. This is because after the pixels relative positions are run through the transformation matrix their offsets will in most cases be float values.
Try to change this line:
game.ctx.clearRect(this.width/-2, this.height/-2, this.width, this.height);
to this instead including compensation for anti-aliased pixels (I'll split the lines for clearity):
game.ctx.clearRect(this.x - this.width/2 - 1, /// remember x and y
this.y - this.height/2 - 1,
this.width + 2,
this.height + 2);
I want to draw text on a canvas in the inverse color of the background (to make sure the text is readible no matter the background color). I believe in oldskool bitblt-ing, this was an XOR operation.
How to do this?
Update: most of the newer browsers now support the blending mode "difference" which can achieve the same result.
context.globalCompositeOperation = "difference";
Updated demo.
Old answer:
One should think that the XOR mode for composition would do this, but unfortunately canvas' XOR only XORs the alpha bits.
By applying the following code we can however receive a result such as this:
You can make an extension to the canvas like this:
CanvasRenderingContext2D.prototype.fillInversedText =
function(txt, x, y) {
//code - see below
}
Now you can call it on the context as the normal fillText, but with a slight change:
ctx.fillInversedText(txt, x, y);
For this to work we do the following first - measure text. Currently we can only calculate width of text and then assume the height. This may or may not work well as fonts can be very tall and so forth. Luckily this will change in the future, but for now:
var tw = this.measureText(txt).width;
var th = parseInt(ctx.font, '10');
th = (th === 0) ? 10 : th; //assume default if no font and size is set
Next thing we need to do is to setup an off-screen canvas to draw the text we want ot invert:
var co = document.createElement('canvas');
co.width = tw;
co.height = th;
Then draw the actual text. Color does not matter as we are only interested in the alpha channel for this canvas:
var octx = co.getContext('2d');
octx.font = this.font;
octx.textBaseline = 'top';
octx.fillText(txt, 0, 0);
Then we extract the pixel buffers for the area we want to draw the inverted text as well as all the pixels for the off-screen canvas which now contains our text:
var ddata = this.getImageData(x, y, tw, th);
var sdata = octx.getImageData(0, 0, tw, th);
var dd = ddata.data; //cache for increased speed
var ds = sdata.data;
var len = ds.length;
And then we invert each pixel where alpha channel for pixel is greater than 0.
for (var i = 0; i < len; i += 4) {
if (ds[i + 3] > 0) {
dd[i] = 255 - dd[i];
dd[i + 1] = 255 - dd[i + 1];
dd[i + 2] = 255 - dd[i + 2];
}
}
Finally put back the inverted image:
this.putImageData(ddata, x, y);
This may seem as a lot of operations, but it goes pretty fast.
Demo (warning if you are sensitive to flicker)
(the psychedelic background is just to have some variations as fiddle needs external images and most are prevented by CORS when we use pixel manipulation).
I've removed my old answer, as it did not solve the question. As of recently, there are new globalCompositeOperations that do all kinds of great things. I've created an example that shows how to obtain inverted text. In case that link breaks, the method is essentially this:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = "white";
//draw inverted things here
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
i have a darkBlueRect rectangle sprite and a copy of the same sprite with a larger scale and a lighter color - lightBlueRect.
i'm attempting to shift the location of the lightBlueRect according to the mouse.x location when the mouse is moving over the darkBlueRect. this way if the mouse is on the right of the darkBlueRect than the location of the scaled up lightBlueRect will be on the opposite side and shifted towards the left proportionate to the mouse position and scale. in addition, the lightBlueRect must appear "locked" to the darkBlueRect so lightBlueRect.x must never be more than darkBlueRect.x and lightBlueRect.x + lightBlueRect.width must never be less than darkBlueRect.x + darkBlueRect.width.
the image below depicts 3 states of what i'm attempting to accomplish:
State A: mouse.x is over darkBlueRect.x = 1 and both sprites are aligned to the left.
State B: mouse.x is in the middle of darkBlueRect and both sprites are aligned to the middle.
State C: mouse.x is on the last pixel of darkBlueRect and both sprites are aligned to the right.
for this example, the darkBlueRect.width is equal to 170 and the lightBlueRect.width is equal to 320, or a scale of 1.89.
each time the mouse changes it's x position over darkBlueRect the following is called. however, while my current code works for the most part, it's not exactly correct. when the mouse.x is over darkBlueRect.x = 1, as shown in State A, the lightBlueRect.x is not property aligned with darkBlueRect and appears less than darkBlueRect.x.
var scale:Number = 1.89;
lightBlueRect.x = darkBlueRect.x - Math.round((mouse.x * scale) / darkBlueRect.width * (lightBlueRect.width - darkBlueRect.width));
what equation can i use so that no matter the scale of the lightBlueRect it's first position (mouse over first pixel) and last position (mouse over last pixel) will result in the 2 sprites being aligned as well as property proportionate positioning in between?
[EDIT] the coordinates of the darkBlueRect is {0, 0}, so when the lightBlueRect moves towards the left it is moving into the negative. i could have simply written my code (what doesn't work) like this instead:
var scale:Number = 1.89;
lightBlueRect.x = 0 - Math.round((mouse.x * scale) / darkBlueRect.width * (lightBlueRect.width - darkBlueRect.width));
[EDIT 2]
when the display objects are small, the problem is difficult to notice. however, when they are large the problem becomes move obvious. the problem, here, being that the objects on the left side are misaligned.
also the problem is probably exasperated by the fact that both the lightBlueRect and darkBlueRect are scalable. darkBlueRect is scaled down and lightBlueRect is scaled up.
here is a link to the test displaying the problem. mousing over the shape quickly will obviously result in inaccurate alignment since it's based on frame rate speed, but this is not my concern. still, when you slowly mouse over the shape it will not align correctly on the left side when the mouse is over the first pixel of darkBlueRect: http://www.geoffreymattie.com/test/test.html
[SWF(width = "1000", height = "600", backgroundColor = "0xCCCCCC")]
import flash.display.Sprite;
import flash.events.MouseEvent;
var downScale:Number = 0.48;
var upScale:Number = 2.64;
var darkBlueRect:Sprite = createSprite();
darkBlueRect.scaleX = darkBlueRect.scaleY = downScale;
darkBlueRect.x = stage.stageWidth / 2 - darkBlueRect.width / 2;
darkBlueRect.y = stage.stageHeight / 2 - darkBlueRect.height / 2;
addChild(darkBlueRect);
var lightBlueRect:Sprite = createSprite();
lightBlueRect.scaleX = lightBlueRect.scaleY = upScale;
lightBlueRect.y = stage.stageHeight / 2 - lightBlueRect.height / 2;
lightBlueRect.x = stage.stageWidth;
lightBlueRect.mouseEnabled = false;
addChild(lightBlueRect);
darkBlueRect.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveEventHandler);
function mouseMoveEventHandler(evt:MouseEvent):void
{
lightBlueRect.x = darkBlueRect.x + Math.max(0.0, Math.min(darkBlueRect.mouseX / darkBlueRect.width * downScale, 1.0)) * (darkBlueRect.width - lightBlueRect.width);
}
function createSprite():Sprite
{
var result:Sprite = new Sprite();
result.graphics.beginFill(0x0000FF, 0.5);
result.graphics.drawRect(0, 0, 700, 200);
result.graphics.endFill();
return result;
}
i believe the problem is that the scaling of the shapes.
Assuming you have a Clamp function handy, and that width is floating-point so that division works as expected:
lBR.x = dBR.x + Clamp((mouse.x - dBR.x) / dBR.width, 0, 1) * (dBR.width - lBR.width);
(You can define Clamp(x, m, M) = min(max(x, m), M) if you don't have one.)
So I am writing a little mini pseudo 3d engine for the canvas element in html5. In the code below I am drawing a bunch of squares with varying positions and rotations (rotation around the z axis, so no deformation)
Now I want to be able to tell which square the user clicks on. In the objects array the items are supported by the z position starting with the squares the furthest away from the camera (so that they draw properly). So given a 3d point relative to the top left of the corner of the canvas how can I tell which square was clicked?
//Draw objects
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
var cz = object.position.z - camera.position.z;
if (cz > 0) {
cz = 1 / ((cz - 1) * 0.75 + 1);
context.save();
context.translate(halfWidth, halfHeight); //viewport transform
context.scale(1, -1); //invert y axis
context.scale(cz, cz); //perspective
context.translate(-camera.position.x, -camera.position.y); //camera transform
context.translate(object.position.x, object.position.y); //world transform
context.rotate(object.rotation);
context.fillStyle = object.color;
context.fillRect(-40, -40, 80, 80);
context.restore();
}
}
P.S. If I am doing anything weird or backwards and you know of a way to improve, I would love to hear suggestions
I would suggest that you draw the objects with the same transformations to a hidden canvas of the same size, but give each square a unique color (maybe derived from the index i).
You would do that like this:
var col = index.toString(16); // convert to hex
while (col.length < 6) col = "0"+col; // pad leading 0s
ctx.fillStyle = "#"+col;
ctx.fillRect(-40,-40,80,80);
Then when you get a mouseclick event on the visible canvas, look at that location in your hidden one to get the color (index) of the selected object:
var colData = ctx.getImageData(clickX, clickY, 1, 1).data;
var index = (colData[2]<<16) | (colData[1]<<8) | colData[0];
This will work for up to 16M objects and is fairly simple.