Disable Interpolation when Scaling a canvas
Last Updated: 2014-09-12
Is there a canvas property or browser setting I can change programmatically to disable interpolation when scaling elements?
The answer is maybe some day. For now, you'll have to resort to hack-arounds to get what you want.
image-rendering
The working draft of CSS3 outlines a new property, image-rendering
that should do what I want:
The image-rendering property provides a hint to the user-agent about what aspects of an image are most important to preserve when the image is scaled, to aid the user-agent in the choice of an appropriate scaling algorithm.
The specification outlines three accepted values: auto
, crisp-edges
, and pixelated
.
pixelated:
When scaling the image up, the "nearest neighbor" or similar algorithm must be used, so that the image appears to be simply composed of very large pixels. When scaling down, this is the same as auto.
Standard? Cross-browser?
Since this is merely a working draft, there's no guarantee that this will become standard. Browser support is currently spotty, at best.
The Mozilla Developer Network has a pretty thorough page dedicated to the current state of the art which I highly recommend reading.
The Webkit developers initially chose to tentatively implement this as -webkit-optimize-contrast
, but Chromium/Chrome don't seem to be using a version of Webkit that implements this.
Update: 2014-09-12
Chrome 38 now supports image-rendering: pixelated
!
Firefox has a bug report open to get image-rendering: pixelated
implemented, but -moz-crisp-edges
works for now.
Solution?
The most cross-platform, CSS-only solution so far is thus:
canvas {
image-rendering: optimizeSpeed; /* Older versions of FF */
image-rendering: -moz-crisp-edges; /* FF 6.0+ */
image-rendering: -webkit-optimize-contrast; /* Safari */
image-rendering: -o-crisp-edges; /* OS X & Windows Opera (12.02+) */
image-rendering: pixelated; /* Awesome future-browsers */
-ms-interpolation-mode: nearest-neighbor; /* IE */
}
Sadly this wont work on all major HTML5 platforms yet (Chrome, in particular).
Of course, one could manually scale up images using nearest-neighbor interpolation onto high-resolution canvas surfaces in javascript, or even pre-scale images server-side, but in my case this will be forbiddingly costly so it is not a viable option.
ImpactJS uses a texture pre-scaling technique to get around all this FUD. Impact's developer, Dominic Szablewski, wrote a very in-depth article about this (he even ended up citing this question in his research).
See Simon's answer for a canvas-based solution that relies on the imageSmoothingEnabled
property (not available in older browsers, but simpler than pre-scaling and pretty widely-supported).
Live Demo
If you'd like to test the CSS properties discussed in the MDN article on canvas
elements, I've made this fiddle which should display something like this, blurry or not, depending on your browser:
How to resize a canvas without anti-aliasing?
If you resize the canvas the smoothing setting is reset as well (may depend on browser).
After you resize the canvas simply re-apply the imageSmoothingEnabled
.
fill-screen = (canvas) ->
canvas.width = document.body.clientWidth
canvas.height = document.body.clientHeight
// re-apply here on context
I would also recommend using window.innerWidth
and window.innerHeight
instead for the sizes.
My (free) retro context library may also be of interest.
How to scale images on a html5 canvas with better interpolation?
You need to "step down" several times. Instead of scaling from a very large image to a very small, you need to re-scale it to intermediary sizes.
Consider an image you want to draw at 1/6 scale. You could do this:
var w = 1280;
var h = 853;
ctx.drawImage(img, 0, 0, w/6, h/6);
Or you could draw it to an in-memory canvas at 1/2 scale, then 1/2 scale again, then 1/2 scale again. The result is a 1/6 scale image, but we use three steps:
var can2 = document.createElement('canvas');
can2.width = w/2;
can2.height = w/2;
var ctx2 = can2.getContext('2d');
ctx2.drawImage(img, 0, 0, w/2, h/2);
ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);
Then you can draw that back to your original context:
ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);
You can see the difference live, here:
var can = document.getElementById('canvas1');var ctx = can.getContext('2d');
var img = new Image();var w = 1280;var h = 853;img.onload = function() { // step it down only once to 1/6 size: ctx.drawImage(img, 0, 0, w/6, h/6); // Step it down several times var can2 = document.createElement('canvas'); can2.width = w/2; can2.height = w/2; var ctx2 = can2.getContext('2d'); // Draw it at 1/2 size 3 times (step down three times) ctx2.drawImage(img, 0, 0, w/2, h/2); ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4); ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6); ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);}
img.src = 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Equus_quagga_%28Namutoni%2C_2012%29.jpg/1280px-Equus_quagga_%28Namutoni%2C_2012%29.jpg'
canvas { border: 1px solid gray;}
<canvas id="canvas1" width="400" height="400"></canvas>
How to disable linear filtering for drawImage on canvas in javafx
UPD 10/07/2019:
Looks like the issue is fixed! Now GraphicsContext should have property "image smoothing" controlling this behavior.
INITIAL ANSWER
I guess I've found answer to my question. As this issue says that there's no way to specify filtering options in graphics context.
Description:
When drawing an image in a GraphicsContext using the drawImage()
method to enlarge a small image to a larger canvas, the image is being
interpolated (possibly using a bilinear or bicubic algorithm). But
there are times like when rendering color maps (temperature,
zooplancton, salinity, etc.) or some geographical data (population
concentration, etc.) where we want to have no interpolation at all
(ie: use the nearest neighbor algorithm instead) in order to represent
accurate data and shapes.In Java2D, this is possible by setting the appropriate
RenderingHints.KEY_RENDERING on the Graphics2D at hand. Currently on
JavaFX's GraphicsContext there is no such way to specify how the image
is to be interpolated.The same applies when shrinking images too.
This could be expanded to support a better form of smoothing for the
"smooth" value that is available in both Image and ImageView and that
does not seem to work very well currently (at least on Windows).
The issue was created in 2013 but it's still untouched so unlikely it will be resolved soon.
Control Scaling of Canvas When dragged out of chart limit
If a dragStart event occurs beyond the scale limits, the increment should be a fixed value to avoid the issue you mentioned. Also, ticks.min
and ticks.max
should be set for the same purpose. Below is a sample jsfiddle and code (you can control speed by step
).
https://jsfiddle.net/oLrk3fb2/
function onDragStart() {
const event = d3.event.sourceEvent;
const scales = chartInstance.scales;
const scaleInstanceY = scales[scaleY];
const scaleInstanceX = scales[scaleX];
const scalesOpts = chartInstance.options.scales;
const ticksOptsX = scalesOpts.xAxes[0].ticks;
const ticksOptsY = scalesOpts.yAxes[0].ticks;
const step = 1;
datasetIndex = element['_datasetIndex'];
index = element['_index'];
valueY = scaleInstanceY.getValueForPixel(event.offsetY);
valueX = scaleInstanceX.getValueForPixel(event.offsetX);
if (valueY < scaleInstanceY.min) {
ticksOptsY.min = valueY = scaleInstanceY.min - step;
}
if (valueY > scaleInstanceY.max) {
ticksOptsY.max = valueY = scaleInstanceY.max + step;
}
if (valueX < scaleInstanceX.min) {
ticksOptsX.min = valueX = scaleInstanceX.min - step;
}
if (valueX > scaleInstanceX.max) {
ticksOptsX.max = valueX = scaleInstanceX.max + step;
}
chartInstance.data.datasets[datasetIndex].data[index] = {
x: valueX,
y: valueY
}
chartInstance.update(0);
}
Is there a way to avoid letterboxing with canvas scaling?
I finally found the answer. I was close to the right track, but a few more things needed to be applied.
application.renderer.view.style.position = "absolute";
application.renderer.view.style.display = "block";
application.renderer.autoResize = true;
application.renderer.resize(window.innerWidth, window.innerHeight);
This sets some additional things internally, while a minor modification to the resize script...
ratio = Math.min(window.innerWidth / GAME_WIDTH, window.innerHeight / GAME_HEIGHT);
stage.scale.x = stage.scale.y = ratio;
renderer.resize(window.innerWidth, window.innerHeight);
configures things correctly, so that the related Renderer window now fills the screen without squashing the content.
This was not easy to discover. So many tutorials just leave it at the first half, and assume that is what people wish to do.
Can I turn off antialiasing on an HTML canvas element?
For images there's now context.imageSmoothingEnabled
= false
.
However, there's nothing that explicitly controls line drawing. You may need to draw your own lines (the hard way) using getImageData
and putImageData
.
Related Topics
How to Apply the Required Attribute to ≪Select≫ Fields in Html5
How to Get a Number of Random Elements from an Array
How to Get Numeric Value from a Prompt Box
Electron Require() Is Not Defined
Chrome Extension - Retrieving Global Variable from Webpage
How to Simulate a Hover With a Touch in Touch Enabled Browsers
When Should I Use Inline Vs. External JavaScript
How to Get All Selected Values of a Multiple Select Box
Open Url in Same Window and in Same Tab
Html Text-Overflow Ellipsis Detection
Refresh/Reload the Content in Div Using Jquery/Ajax
How to Hide the Cursor in a Webpage Using CSS or JavaScript
JavaScript Function Doesn't Work When Link Is Clicked
How to Apply !Important Using .Css()
How to Launch HTML Using Chrome At "--Allow-File-Access-From-Files" Mode
Is There a Spec That the Id of Elements Should Be Made Global Variable
What Does "./" (Dot Slash) Refer to in Terms of an HTML File Path Location