Cause
Some images are just very hard to down-sample and interpolate such as this one with curves when you want to go from a large size to a small.
Browsers appear to typically use bi-linear (2x2 sampling) interpolation with the canvas element rather than bi-cubic (4x4 sampling) for (likely) performance reasons.
If the step is too huge then there are simply not enough pixels to sample from which is reflected in the result.
From a signal/DSP perspective you could see this as a low-pass filter's threshold value set too high, which may result in aliasing if there are many high frequencies (details) in the signal.
Solution
Update 2018:
Here's a neat trick you can use for browsers which supports the filter
property on the 2D context. This pre-blurs the image which is in essence the same as a resampling, then scales down. This allows for large steps but only needs two steps and two draws.
Pre-blur using number of steps (original size / destination size / 2) as radius (you may need to adjust this heuristically based on browser and odd/even steps - here only shown simplified):
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
if (typeof ctx.filter === "undefined") {
alert("Sorry, the browser doesn't support Context2D filters.")
}
const img = new Image;
img.onload = function() {
// step 1
const oc = document.createElement('canvas');
const octx = oc.getContext('2d');
oc.width = this.width;
oc.height = this.height;
// steo 2: pre-filter image using steps as radius
const steps = (oc.width / canvas.width)>>1;
octx.filter = `blur(${steps}px)`;
octx.drawImage(this, 0, 0);
// step 3, draw scaled
ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);
}
img.src = "//i.stack.imgur.com/cYfuM.jpg";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<br/><p>Original was 1600x1200, reduced to 400x300 canvas</p><br/>
<canvas id="canvas" width=400 height=250></canvas>
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…