Is it possible to control canvas texture upscale behavior? - html

When upscaling a texture in canvas, the behavior differs greatly across browsers.
Here is a fiddle creating a 2x1 texture (composed of one black and one white texel) and applying it on a 256x128px rectangle: https://jsfiddle.net/ozirus/x1c3m50o/
var texture = ...
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 512;
ctx.canvas.height = 512;
document.body.appendChild(ctx.canvas);
ctx.setTransform(128, 0, 0, 128, 0, 0);
ctx.fillStyle = ctx.createPattern(texture, "no-repeat");;
ctx.rect(0, 0, 2, 1);
ctx.fill("evenodd");
The following screenshots showcase the results when run in different browsers.
In Chrome:
In Firefox:
In IE11/Edge:
Are there any flags/options available to control this behavior and make it more consistent?
The IE/Edge behavior where the last pixels are the result of a wrapping/repeating interpolation is the major issue I'm actually trying to solve.
PS: I am aware of some workarounds that could do the trick (half texel offset + scale up, transparent border on textures, ...) but I'd rather configure this behavior if possible.
EDIT: as pointed out by #Ryan in the comments, CanvasRenderingContext2D.imageSmoothingEnabled property can be used to have matching behaviors in nearest neighbour mode but I want to keep smooth interpolation of the textures

No.
No there is no way to access the code behind the 2D API (CPU and GPU) from javascript. The standard has many holes and thus browser developers are left to interpret it however it suits them.
If you use "repeat" you will get the same result as "no-repeat" on Edge on all 3 browsers.
On Edge, Firefox and Chrome with
ctx.fillStyle = ctx.createPattern(textureCanvas, "repeat");;
If you want the result to be like FF then use ctx.drawImage to render the bitmap. If you want irregular shapes then render using the ctx.drawImage then mask with a path and ctx.globalCompositeOperation = "destination-in". If you need to render to existing canvas content then use a second canvas to render the bitmaps and masks and then render that canvas onto the existing content.
On Edge, Firefox and Chrome with
ctx.drawImage(textureCanvas,0,1,2,1);
If you want the same result as Chrome on all 3 browsers you need to add a one pixel transparent border around the image and then render the 2 pixel center.
Same result on Firefox, Edge and chrome in example below (copied and modified from your fiddle https://jsfiddle.net/ozirus/x1c3m50o/)
var textureCanvas = document.createElement("canvas");
textureCanvas.width = 4;
textureCanvas.height = 3;
var textureContext = textureCanvas.getContext("2d");
var imgData = textureContext.createImageData(4, 3);
var i = 20;
imgData.data[i++] = 0;
imgData.data[i++] = 0;
imgData.data[i++] = 0;
imgData.data[i++] = 255;
imgData.data[i++] = 255;
imgData.data[i++] = 255;
imgData.data[i++] = 255;
imgData.data[i++] = 255;
textureContext.putImageData(imgData, 0, 0);
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 512;
ctx.canvas.height = 512;
document.body.appendChild(ctx.canvas);
ctx.setTransform(128, 0, 0, 128, 0, 0);
ctx.drawImage(textureCanvas,1,1,2,1,0,0,2,1);
html {
background-color: red;
}
Yes.
So I guess that means you can control it, but indirectly using different techniques.

Related

HTML canvas: Why does a large shadow blur not show up for small objects?

Here's a demonstration:
var ctx = document.getElementById("test").getContext("2d");
ctx.shadowColor = "black";
ctx.fillStyle = "white";
ctx.shadowBlur = 10;
ctx.fillRect(10, 10, 10, 10);
ctx.shadowBlur = 50;
ctx.fillRect(70, 10, 10, 10);
ctx.fillRect(70, 70, 70, 70);
<canvas id="test" width="200" height="200"></canvas>
If I set shadowBlur=10 and then draw a small 10x10 square, I get a nice, strong shadow. The same if I set shadowBlur=50 and draw a big 70x70 square. But if I set shadowBlur=50 and then draw a small 10x10 square, I get a very faint, barely visible shadow.
Instead I would have expected a small center square and a large dark shadow all around it.
Obviously I misunderstand how the shadow blur works, so - how does it work, and how do I get a large dark shadow around a small object?
The shadowBlur uses Gaussian blur to produce the shadow internally. The object is drawn to a separate bitmap as stencil in the shadow-color and then blurred using the radius. It does not use the original shape after this step. The result is composited back (as a side-note: there was previously a disagreement on how to composite shadows so Firefox and Chrome/Opera rendered them differently - I think they have landed on source-over in both camps by now though).
If the object is very small and the blur radius very big, the averaging will be thinned by the empty remaining space around the object leaving a more faint shadow.
The only way to get a more visible shadow with the built-in method is to use a smaller radius. You can also "cheat" using a radial gradient, or draw a bigger object with shadow applied to an off-screen canvas but offset relative to the shadow itself so the object doesn't overlap it, then draw the shadow only (using clipping arguments with drawImage()) back to main canvas at desired size before drawing main object.
In newer versions of the browsers you can also produce Gaussian blurred shadows manually using the new filter property on the context with CSS filters. It do require some extra compositing steps and most likely an off-screen canvas for most scenarios, but you can with this method overdraw shadows in multiple steps with variable radii from small to bigger producing a more pronounced shadow at the cost of some performance.
Example of manually generated shadow using filter:
This allow for more complex shapes like with the built-in shadow, but offer more control of the end result. "Falloff" in this case can be controlled by using a easing-function with an initial normalized radius value inside the loop.
// note: requires filter support on context
var ctx = c.getContext("2d");
var iterations = 16, radius = 50,
step = radius / iterations;
for(var i = 1; i < iterations; i++) {
ctx.filter = "blur(" + (step * i) + "px)";
ctx.fillRect(100, 50, 10, 10);
}
ctx.filter = "none";
ctx.fillStyle = "#fff";
ctx.fillRect(100, 50, 10, 10);
<canvas id=c></canvas>
Example of gradient + filter:
This is a more cross-browser friendly solutions as if filter is not supported, at least the gradient comes close to an acceptable shadow. The only drawback is it is more limited in regards to complex shapes.
Additionally, using a variable center point for the gradient allows for mimicking fall-off, light size, light type etc.
Based on #Kaiido's example/mod in comment -
// note: requires filter support on context
var ctx = c.getContext("2d");
var grad = ctx.createRadialGradient(105,55,50,105,55,0);
grad.addColorStop(0,"transparent");
grad.addColorStop(0.33,"rgba(0,0,0,0.5)"); // extra point to control "fall-off"
grad.addColorStop(1,"black");
ctx.fillStyle = grad;
ctx.filter = "blur(10px)";
ctx.fillRect(0, 0, 300, 150);
ctx.filter = "none";
ctx.fillStyle = "#fff";
ctx.fillRect(100, 50, 10, 10);
<canvas id=c></canvas>

Pixels twice its size

I just created little pixel art for my project http://i.imgur.com/9FLj6Uk.png. And I want to use it on my site. As you can see, it's small pixel art. I want one pixel to be drawn as 2*2 pixels instead of 1*1 pixel. I would redraw the picture with using 2*2 pixels instead of one but that seems to be bad solution.
I tried to use CSS on it
img.pixel {
width: 32px;
height: 32px
}
but that doesn't work, it shows weird shades in my browser. I want to see hard pixels. Does anyone know any solution for this problem?
This is the problem when I use the CSS above
Pixel art is tough in browsers, mainly due to lack of universal browser support of "pixelated" or "crisp-edges" image rendering. It should be supported in CSS4.
Currently the CSS stack looks like this, although it looks as if Chrome 30 and Opera 16 have broken support for CSS solutions
image-rendering:optimizeSpeed;
image-rendering:-moz-crisp-edges;
image-rendering:-o-crisp-edges;
image-rendering:optimize-contrast;
image-rendering:-webkit-optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
See this answer by #Phrogz, with a test case. Also see mozilla's documentation on the subject. For universal support now, a JS solution may have to work for the time being such as seen here on the great article drawing pixels is hard:
var resize = function( img, scale ) {
// Takes an image and a scaling factor and returns the scaled image
// The original image is drawn into an offscreen canvas of the same size
// and copied, pixel by pixel into another offscreen canvas with the
// new size.
var widthScaled = img.width * scale;
var heightScaled = img.height * scale;
var orig = document.createElement('canvas');
orig.width = img.width;
orig.height = img.height;
var origCtx = orig.getContext('2d');
origCtx.drawImage(img, 0, 0);
var origPixels = origCtx.getImageData(0, 0, img.width, img.height);
var scaled = document.createElement('canvas');
scaled.width = widthScaled;
scaled.height = heightScaled;
var scaledCtx = scaled.getContext('2d');
var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
var indexScaled = (y * widthScaled + x) * 4;
scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
}
}
scaledCtx.putImageData( scaledPixels, 0, 0 );
return scaled;
}
Read the article through, the presence of retina displays and mobile safari may add additional complexity to rendering the correct size pixel art. Although with iOS7's mobile safari this may be rectified.
I suggest you to use an svg image, as that will be scalable and get you what you are looking for.
You can read more about the same from the below link.
https://developer.mozilla.org/en/docs/SVG_In_HTML_Introduction
Hope this helps.
Are you looking for this? <img src="http://i.imgur.com/9FLj6Uk.png width="32" height="32">
check fiddle

HTML5: Inverse text-color on canvas

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

makin an image black and white / grayscale in html

Is there any way to convert the image so it appears as a gray scale. Changing rgb is not working as required.
As you probably know, screen color consists of 3 components: red, green and blue. Each component or color (for example, red) has a value from 0 to 255. The value 0 indicates there is no red color and the value 255 would be the brightest possible red color. To convert a color to grayscale you just need to calculate the average for all three components. This can be done using the simple formula below:
grayscalecolor = (red + green + blue) / 3;
Canvas Method
var canvas = document.createElement('canvas');
var canvasContext = canvas.getContext('2d');
var imgW = imgObj.width;
var imgH = imgObj.height;
canvas.width = imgW;
canvas.height = imgH;
canvasContext.drawImage(imgObj, 0, 0);
var imgPixels = canvasContext.getImageData(0, 0, imgW, imgH);
for(>var y = 0; y < imgPixels.height; y++){
for(>var x = 0; x < imgPixels.width; x++){
var i = (y * 4) * imgPixels.width + x * 4;
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
}
}
canvasContext.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
return canvas.toDataURL();
Microsoft Internet Explorer does not support the CANVAS tag, but Microsoft does provide image manipulation through filters. The filter to use for converting images to grayscale is the filter named DXImageTransform.Microsoft.BasicImage.
imgObj.style.filter = 'progid:DXImageTransform.Microsoft.BasicImage(grayScale=1)';
If you're able to use HTML5 you could use a canvas, as seen here.
Google for "canvas grayscale image" the first link will be:
HTML 5 introduces Canvas object which can be used to draw and manipulate images. In this example, I used a Canvas to turn an image into grayscale. If your browser supports Canvas, you should see two images above: the normal image and the grayscale one. The grayscale one is not a separate image, it is generated by utilizing Canvas.
http://permadi.com/tutorial/jsCanvasGrayscale/index.html
If you want this to be done client side (on the browser) with a HTML you will need to use a Canvas. Example here: http://www.youtube.com/watch?v=QJa7tWScXS4
Use PaintbrushJS:
http://mezzoblue.github.com/PaintbrushJS/demo/
It lets you apply any number of filters including greyscale and works in most modern browsers. It's not a 'HTML' solution, that would not be possible without two images.
At least it works on most browsers, including ones which don't yet support HTML5.

HTML Canvas: update globalAlpha property

I'm trying to update the globalAlpha of a canvas element after its painted. The canvas contains an image, and two texts. I show the code at the bottom.
I want to change the globalAlpha property from 0.6 to 1 afterwards. How can i do this most elegantly with performance in mind?
var ctx = canvasElement.getContext("2d");
ctx.globalAlpha = 0.6;
//background gradient
var gradient = ctx.createLinearGradient(0, 0, 0, options["height"]);
gradient.addColorStop(0, options["colorStop0"]);
gradient.addColorStop(1, options["colorStop1"]);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, options["width"], options["height"]);
//program
ctx.font = options.programText["font"];
ctx.fillStyle = options.programText["color"];
ctx.fillText(prog.title, options.programText["x"], options.programText["y"]);
//channel number
ctx.font = options.channelNumber["font"];
ctx.fillStyle = options.channelNumber["color"];
ctx.fillText(item_index + 1, options.channelNumber["x"], options.channelNumber["y"]);
//channel logo
var channelLogo = new Image();
channelLogo.onload = function () {
var clX = options.channelLogo["x"];
var clY = options.channelLogo["y"];
ctx.drawImage(channelLogo, clX, clY, channelLogo.width, channelLogo.height);
};
channelLogo.src = this.getChannelLogo(channel);
You cannot update the globalAlpha of a shape after it is painted.
More to the point, you cannot "update" anything after it is painted. There is no updating in Canvas. You must instead redraw the thing(s) with the attributes changed.
So in your case you will simply want to clear your canvas and redraw everything with globalAlpha set back to 1.
You can update canvas after painting by just doing pixel manipulation.
Using the getImageData & putImageData method of context of canvas.
But there is not any method like "changeAlpha(x,y,width,height,alphaValue)" of context of canvas.
Same kind of problem i am also facing in my app.
If your alpha is going up to 100%, you can probably just repaint it without redrawing the whole scene (overwriting what was there previously). The only risk is some antialiasing artifacts if you're doing curves.
If you alpha is going down, however, you have to do as Simon says - repaint the whole scene.