Merge pull request #360 from juliandescottes/improve-straight-lines
Improve straight lines
This commit is contained in:
commit
a928a2819f
7 changed files with 202 additions and 93 deletions
|
@ -82,44 +82,4 @@
|
||||||
|
|
||||||
ns.BaseTool.prototype.releaseToolAt = function (col, row, frame, overlay, event) {};
|
ns.BaseTool.prototype.releaseToolAt = function (col, row, frame, overlay, event) {};
|
||||||
|
|
||||||
/**
|
|
||||||
* Bresenham line algorithm: Get an array of pixels from
|
|
||||||
* start and end coordinates.
|
|
||||||
*
|
|
||||||
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
|
|
||||||
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ns.BaseTool.prototype.getLinePixels_ = function (x0, x1, y0, y1) {
|
|
||||||
x1 = pskl.utils.normalize(x1, 0);
|
|
||||||
y1 = pskl.utils.normalize(y1, 0);
|
|
||||||
|
|
||||||
var pixels = [];
|
|
||||||
var dx = Math.abs(x1 - x0);
|
|
||||||
var dy = Math.abs(y1 - y0);
|
|
||||||
var sx = (x0 < x1) ? 1 : -1;
|
|
||||||
var sy = (y0 < y1) ? 1 : -1;
|
|
||||||
var err = dx - dy;
|
|
||||||
while (true) {
|
|
||||||
|
|
||||||
// Do what you need to for this
|
|
||||||
pixels.push({'col': x0, 'row': y0});
|
|
||||||
|
|
||||||
if ((x0 == x1) && (y0 == y1)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var e2 = 2 * err;
|
|
||||||
if (e2 > -dy) {
|
|
||||||
err -= dy;
|
|
||||||
x0 += sx;
|
|
||||||
}
|
|
||||||
if (e2 < dx) {
|
|
||||||
err += dx;
|
|
||||||
y0 += sy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pixels;
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
// The pen movement is too fast for the mousemove frequency, there is a gap between the
|
// The pen movement is too fast for the mousemove frequency, there is a gap between the
|
||||||
// current point and the previously drawn one.
|
// current point and the previously drawn one.
|
||||||
// We fill the gap by calculating missing dots (simple linear interpolation) and draw them.
|
// We fill the gap by calculating missing dots (simple linear interpolation) and draw them.
|
||||||
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
|
var interpolatedPixels = pskl.PixelUtils.getLinePixels(col, this.previousCol, row, this.previousRow);
|
||||||
for (var i = 0, l = interpolatedPixels.length ; i < l ; i++) {
|
for (var i = 0, l = interpolatedPixels.length ; i < l ; i++) {
|
||||||
var coords = interpolatedPixels[i];
|
var coords = interpolatedPixels[i];
|
||||||
this.applyToolAt(coords.col, coords.row, frame, overlay, event);
|
this.applyToolAt(coords.col, coords.row, frame, overlay, event);
|
||||||
|
|
|
@ -90,13 +90,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
ns.Stroke.prototype.draw_ = function (col, row, color, targetFrame, penSize, isStraight) {
|
ns.Stroke.prototype.draw_ = function (col, row, color, targetFrame, penSize, isStraight) {
|
||||||
|
var linePixels;
|
||||||
if (isStraight) {
|
if (isStraight) {
|
||||||
var coords = this.getStraightLineCoordinates_(col, row);
|
linePixels = pskl.PixelUtils.getUniformLinePixels(this.startCol, col, this.startRow, row);
|
||||||
col = coords.col;
|
} else {
|
||||||
row = coords.row;
|
linePixels = pskl.PixelUtils.getLinePixels(col, this.startCol, row, this.startRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
var linePixels = this.getLinePixels_(this.startCol, col, this.startRow, row);
|
|
||||||
pskl.PixelUtils.resizePixels(linePixels, penSize).forEach(function (point) {
|
pskl.PixelUtils.resizePixels(linePixels, penSize).forEach(function (point) {
|
||||||
targetFrame.setPixel(point[0], point[1], color);
|
targetFrame.setPixel(point[0], point[1], color);
|
||||||
});
|
});
|
||||||
|
@ -108,50 +108,4 @@
|
||||||
this.draw_(replayData.col, replayData.row, replayData.color, frame, replayData.penSize, replayData.isStraight);
|
this.draw_(replayData.col, replayData.row, replayData.color, frame, replayData.penSize, replayData.isStraight);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert col, row to be on the nearest straight line or 45 degrees diagonal.
|
|
||||||
*/
|
|
||||||
ns.Stroke.prototype.getStraightLineCoordinates_ = function(col, row) {
|
|
||||||
// coordinates translated using startCol, startRow as origin
|
|
||||||
var tCol = col - this.startCol;
|
|
||||||
var tRow = this.startRow - row;
|
|
||||||
|
|
||||||
var dist = Math.sqrt(Math.pow(tCol, 2) + Math.pow(tRow, 2));
|
|
||||||
|
|
||||||
var axisDistance = Math.round(dist);
|
|
||||||
var diagDistance = Math.round(dist / Math.sqrt(2));
|
|
||||||
|
|
||||||
var PI8 = Math.PI / 8;
|
|
||||||
var angle = Math.atan2(tRow, tCol);
|
|
||||||
if (angle < PI8 && angle >= -PI8) {
|
|
||||||
row = this.startRow;
|
|
||||||
col = this.startCol + axisDistance;
|
|
||||||
} else if (angle >= PI8 && angle < 3 * PI8) {
|
|
||||||
row = this.startRow - diagDistance;
|
|
||||||
col = this.startCol + diagDistance;
|
|
||||||
} else if (angle >= 3 * PI8 && angle < 5 * PI8) {
|
|
||||||
row = this.startRow - axisDistance;
|
|
||||||
col = this.startCol;
|
|
||||||
} else if (angle >= 5 * PI8 && angle < 7 * PI8) {
|
|
||||||
row = this.startRow - diagDistance;
|
|
||||||
col = this.startCol - diagDistance;
|
|
||||||
} else if (angle >= 7 * PI8 || angle < -7 * PI8) {
|
|
||||||
row = this.startRow;
|
|
||||||
col = this.startCol - axisDistance;
|
|
||||||
} else if (angle >= -7 * PI8 && angle < -5 * PI8) {
|
|
||||||
row = this.startRow + diagDistance;
|
|
||||||
col = this.startCol - diagDistance;
|
|
||||||
} else if (angle >= -5 * PI8 && angle < -3 * PI8) {
|
|
||||||
row = this.startRow + axisDistance;
|
|
||||||
col = this.startCol;
|
|
||||||
} else if (angle >= -3 * PI8 && angle < -PI8) {
|
|
||||||
row = this.startRow + diagDistance;
|
|
||||||
col = this.startCol + diagDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
col : col,
|
|
||||||
row : row
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
ns.LassoSelect.prototype.getLassoPixels_ = function () {
|
ns.LassoSelect.prototype.getLassoPixels_ = function () {
|
||||||
var line = this.getLinePixels_(this.previousCol, this.startCol, this.previousRow, this.startRow);
|
var line = pskl.PixelUtils.getLinePixels(this.previousCol, this.startCol, this.previousRow, this.startRow);
|
||||||
return this.pixels.concat(line);
|
return this.pixels.concat(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
row = pskl.utils.Math.minmax(row, 0, frame.getHeight() - 1);
|
row = pskl.utils.Math.minmax(row, 0, frame.getHeight() - 1);
|
||||||
|
|
||||||
// line interpolation needed in case mousemove was too fast
|
// line interpolation needed in case mousemove was too fast
|
||||||
var interpolatedPixels = this.getLinePixels_(col, this.previousCol, row, this.previousRow);
|
var interpolatedPixels = pskl.PixelUtils.getLinePixels(col, this.previousCol, row, this.previousRow);
|
||||||
this.pixels = this.pixels.concat(interpolatedPixels);
|
this.pixels = this.pixels.concat(interpolatedPixels);
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
|
|
|
@ -4,6 +4,15 @@
|
||||||
ns.Math = {
|
ns.Math = {
|
||||||
minmax : function (val, min, max) {
|
minmax : function (val, min, max) {
|
||||||
return Math.max(Math.min(val, max), min);
|
return Math.max(Math.min(val, max), min);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the distance between {x0, y0} and {x1, y1}
|
||||||
|
*/
|
||||||
|
distance : function (x0, x1, y0, y1) {
|
||||||
|
var dx = Math.abs(x1 - x0);
|
||||||
|
var dy = Math.abs(y1 - y0);
|
||||||
|
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -234,6 +234,97 @@
|
||||||
*/
|
*/
|
||||||
calculateZoomForContainer : function (container, pictureHeight, pictureWidth) {
|
calculateZoomForContainer : function (container, pictureHeight, pictureWidth) {
|
||||||
return this.calculateZoom(container.height(), container.width(), pictureHeight, pictureWidth);
|
return this.calculateZoom(container.height(), container.width(), pictureHeight, pictureWidth);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bresenham line algorithm: Get an array of pixels from
|
||||||
|
* start and end coordinates.
|
||||||
|
*
|
||||||
|
* http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
|
||||||
|
* http://stackoverflow.com/questions/4672279/bresenham-algorithm-in-javascript
|
||||||
|
*/
|
||||||
|
getLinePixels : function (x0, x1, y0, y1) {
|
||||||
|
var pixels = [];
|
||||||
|
|
||||||
|
x1 = pskl.utils.normalize(x1, 0);
|
||||||
|
y1 = pskl.utils.normalize(y1, 0);
|
||||||
|
|
||||||
|
var dx = Math.abs(x1 - x0);
|
||||||
|
var dy = Math.abs(y1 - y0);
|
||||||
|
|
||||||
|
var sx = (x0 < x1) ? 1 : -1;
|
||||||
|
var sy = (y0 < y1) ? 1 : -1;
|
||||||
|
|
||||||
|
var err = dx - dy;
|
||||||
|
while (true) {
|
||||||
|
// Do what you need to for this
|
||||||
|
pixels.push({'col': x0, 'row': y0});
|
||||||
|
|
||||||
|
if ((x0 == x1) && (y0 == y1)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var e2 = 2 * err;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x0 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y0 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a uniform line using the same number of pixel at each step, between the provided
|
||||||
|
* origin and target coordinates.
|
||||||
|
*/
|
||||||
|
getUniformLinePixels : function (x0, x1, y0, y1) {
|
||||||
|
var pixels = [];
|
||||||
|
|
||||||
|
x1 = pskl.utils.normalize(x1, 0);
|
||||||
|
y1 = pskl.utils.normalize(y1, 0);
|
||||||
|
|
||||||
|
var dx = Math.abs(x1 - x0) + 1;
|
||||||
|
var dy = Math.abs(y1 - y0) + 1;
|
||||||
|
|
||||||
|
var sx = (x0 < x1) ? 1 : -1;
|
||||||
|
var sy = (y0 < y1) ? 1 : -1;
|
||||||
|
|
||||||
|
var ratio = Math.max(dx, dy) / Math.min(dx, dy);
|
||||||
|
// in pixel art, lines should use uniform number of pixels for each step
|
||||||
|
var pixelStep = Math.round(ratio) || 0;
|
||||||
|
|
||||||
|
if (pixelStep > Math.min(dx, dy)) {
|
||||||
|
pixelStep = Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxDistance = pskl.utils.Math.distance(x0, x1, y0, y1);
|
||||||
|
|
||||||
|
var x = x0;
|
||||||
|
var y = y0;
|
||||||
|
var i = 0;
|
||||||
|
while (true) {
|
||||||
|
i++;
|
||||||
|
|
||||||
|
pixels.push({'col': x, 'row': y});
|
||||||
|
if (pskl.utils.Math.distance(x0, x, y0, y) >= maxDistance) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAtStep = i % pixelStep === 0;
|
||||||
|
if (dx >= dy || isAtStep) {
|
||||||
|
x += sx;
|
||||||
|
}
|
||||||
|
if (dy >= dx || isAtStep) {
|
||||||
|
y += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
95
test/js/utils/PixelUtilsTest.js
Normal file
95
test/js/utils/PixelUtilsTest.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
describe("PixelUtils test suite", function() {
|
||||||
|
|
||||||
|
beforeEach(function() {});
|
||||||
|
afterEach(function() {});
|
||||||
|
|
||||||
|
var checkPixel = function (pixel, col, row) {
|
||||||
|
expect(pixel.col).toBe(col);
|
||||||
|
expect(pixel.row).toBe(row);
|
||||||
|
};
|
||||||
|
|
||||||
|
var checkPixels = function (pixels /*, expectedPoints ... */) {
|
||||||
|
var expectedPoints = Array.prototype.slice.call(arguments, 1);
|
||||||
|
pixels.forEach(function (pixel, i) {
|
||||||
|
checkPixel(pixel, expectedPoints[i][0], expectedPoints[i][1]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var logPixels = function (pixels) {
|
||||||
|
var buffer = [];
|
||||||
|
pixels.forEach(function (p) {
|
||||||
|
buffer.push('[' + p.col + ',' + p.row + ']');
|
||||||
|
});
|
||||||
|
console.log(buffer.join(','));
|
||||||
|
};
|
||||||
|
|
||||||
|
it("calculates line pixels", function() {
|
||||||
|
// single point
|
||||||
|
console.log('Check getLinePixels(0, 0, 0, 0)');
|
||||||
|
var pixels = pskl.PixelUtils.getLinePixels(0, 0, 0, 0);
|
||||||
|
expect(pixels.length).toBe(1);
|
||||||
|
checkPixel(pixels[0], 0, 0);
|
||||||
|
|
||||||
|
// 2-points line
|
||||||
|
console.log('Check getLinePixels(0, 1, 0, 1)');
|
||||||
|
pixels = pskl.PixelUtils.getLinePixels(0, 1, 0, 1);
|
||||||
|
expect(pixels.length).toBe(2);
|
||||||
|
checkPixel(pixels[0], 0, 0);
|
||||||
|
checkPixel(pixels[1], 1, 1);
|
||||||
|
|
||||||
|
// same line as before, reversed order
|
||||||
|
console.log('Check getLinePixels(1, 0, 1, 0)');
|
||||||
|
pixels = pskl.PixelUtils.getLinePixels(1, 0, 1, 0);
|
||||||
|
expect(pixels.length).toBe(2);
|
||||||
|
// check pixels are returned in reverse order
|
||||||
|
checkPixel(pixels[0], 1, 1);
|
||||||
|
checkPixel(pixels[1], 0, 0);
|
||||||
|
|
||||||
|
// non trivial line
|
||||||
|
console.log('Check getLinePixels(0, 2, 0, 7)');
|
||||||
|
pixels = pskl.PixelUtils.getLinePixels(0, 2, 0, 7);
|
||||||
|
expect(pixels.length).toBe(8);
|
||||||
|
checkPixels(pixels, [0,0],[0,1],[1,2],[1,3],[1,4],[1,5],[2,6],[2,7]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calculates uniform line pixels", function() {
|
||||||
|
// single point
|
||||||
|
console.log('Check getUniformLinePixels(0, 0, 0, 0)');
|
||||||
|
var pixels = pskl.PixelUtils.getUniformLinePixels(0, 0, 0, 0);
|
||||||
|
expect(pixels.length).toBe(1);
|
||||||
|
checkPixel(pixels[0], 0, 0);
|
||||||
|
|
||||||
|
// 2-points line
|
||||||
|
console.log('Check getUniformLinePixels(0, 1, 0, 1)');
|
||||||
|
pixels = pskl.PixelUtils.getUniformLinePixels(0, 1, 0, 1);
|
||||||
|
expect(pixels.length).toBe(2);
|
||||||
|
checkPixel(pixels[0], 0, 0);
|
||||||
|
checkPixel(pixels[1], 1, 1);
|
||||||
|
|
||||||
|
// same line as before, reversed order
|
||||||
|
console.log('Check getUniformLinePixels(1, 0, 1, 0)');
|
||||||
|
pixels = pskl.PixelUtils.getUniformLinePixels(1, 0, 1, 0);
|
||||||
|
expect(pixels.length).toBe(2);
|
||||||
|
// check pixels are returned in reverse order
|
||||||
|
checkPixel(pixels[0], 1, 1);
|
||||||
|
checkPixel(pixels[1], 0, 0);
|
||||||
|
|
||||||
|
// computed step should be 2, min dist is 3 (y1 -y0 + 1)-> step should be applied
|
||||||
|
console.log('Check getUniformLinePixels(0, 5, 0, 2)');
|
||||||
|
pixels = pskl.PixelUtils.getUniformLinePixels(0, 5, 0, 2);
|
||||||
|
expect(pixels.length).toBe(6);
|
||||||
|
checkPixels(pixels, [0,0],[1,0],[2,1],[3,1],[4,2],[5,2]);
|
||||||
|
|
||||||
|
// computed step should be 3, min dist is 2 (y1 -y0 + 1)-> step > minDist -> straight line
|
||||||
|
console.log('Check getUniformLinePixels(0, 5, 0, 1)');
|
||||||
|
pixels = pskl.PixelUtils.getUniformLinePixels(0, 5, 0, 1);
|
||||||
|
expect(pixels.length).toBe(7);
|
||||||
|
checkPixels(pixels, [0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0]);
|
||||||
|
|
||||||
|
// non trivial line
|
||||||
|
console.log('Check getUniformLinePixels(0, 2, 0, 7)');
|
||||||
|
pixels = pskl.PixelUtils.getUniformLinePixels(0, 2, 0, 7);
|
||||||
|
expect(pixels.length).toBe(8);
|
||||||
|
checkPixels(pixels, [0,0],[0,1],[0,2],[1,3],[1,4],[1,5],[2,6],[2,7]);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue