Reputation: 141
I'm using nearest neighbor algorithm to zoom the image on canvas. But, when I move the scaling bar higher, the image have white line that create a square array
Original Image
After I move the scale bar
The zoom is work but the problem is only the white lines. For the source code I will provide in bottom
1.html
<!DOCTYPE HTML>
<html>
<head>
<title>Prototype PC</title>
</head>
<body>
<canvas id='canvas1'></canvas>
<hr>
<button id='read'>READ IMAGE</button>
<hr>
Scale <input type='range' value='1' min='1' max='5' step='0.25' id='scale'>
<br><button id='default2'>Default Scalling</button>
<hr/>
</body>
<style>
body{
background : rgba(255,255,255,1);
}
</style>
<script src='imagine.js'></script>
<script>
var canvas = document.getElementById('canvas1')
var obj = new pc(canvas)
obj.image2canvas("565043_553561101348179_1714194038_a.jpg")
var tes = new Array()
document.getElementById('read').addEventListener('click',function(){
tes = obj.image2read()
})
document.getElementById('scale').addEventListener('change',function(){
var scaleval = this.value
var xpos = 0
var ypos = 0
var xnow = 0
var ynow = 0
var objW = obj.width
var objH = obj.height
tesbackup = new Array()
for(var c=0; c<tes.length; c++){
temp = new Array()
for(var d=0; d<4; d++){
temp.push(255)
}
tesbackup.push(temp)
}
//end of copy
for(var i=0; i<tes.length; i++){
xpos = obj.i2x(i)
ypos = obj.i2y(i)
xnow = Math.round(xpos) * scaleval)
ynow = Math.round(ypos) * scaleval)
if (xnow < objW && ynow < objH) {
for (var j=0; j<scaleval; j++) {
for (var k=0; k<scaleval; k++) {
var idxnow = obj.xy2i(xnow,ynow)
tesbackup[idxnow][0] = tes[i][0]
tesbackup[idxnow][1] = tes[i][1]
tesbackup[idxnow][2] = tes[i][2]
}
}
}
}
obj.array2canvas(tesbackup)
})
</script>
and, for imagine.js
function info(text){
console.info(text)
}
function pc(canvas){
this.canvas = canvas
this.context = this.canvas.getContext('2d')
this.width = 0
this.height = 0
this.imgsrc = ""
this.image2read = function(){
this.originalLakeImageData = this.context.getImageData(0,0, this.width, this.height)
this.resultArr = new Array()
this.tempArr = new Array()
this.tempCount = 0
for(var i=0; i<this.originalLakeImageData.data.length; i++){
this.tempCount++
this.tempArr.push(this.originalLakeImageData.data[i])
if(this.tempCount == 4){
this.resultArr.push(this.tempArr)
this.tempArr = []
this.tempCount = 0
}
}
info('image2read Success ('+this.imgsrc+') : '+this.width+'x'+this.height)
return this.resultArr
}
this.image2canvas = function(imgsrc){
var imageObj = new Image()
var parent = this
imageObj.onload = function() {
parent.canvas.width = imageObj.width
parent.canvas.height = imageObj.height
parent.context.drawImage(imageObj, 0, 0)
parent.width = imageObj.width
parent.height = imageObj.height
info('image2canvas Success ('+imgsrc+')')
}
imageObj.src = imgsrc
this.imgsrc = imgsrc
}
this.array2canvas = function(arr){
this.imageData = this.context.getImageData(0,0, this.width, this.height)
if(this.imageData.data.length != arr.length*4) {
return false
}
for(var i = 0; i < arr.length; i++){
this.imageData.data[(i*4)] = arr[i][0]
this.imageData.data[(i*4)+1] = arr[i][1]
this.imageData.data[(i*4)+2] = arr[i][2]
this.imageData.data[(i*4)+3] = arr[i][3]
}
this.context.clearRect(0, 0, this.width, this.height)
this.context.putImageData(this.imageData, 0, 0)
info('Array2Canvas Success ('+this.imgsrc+')')
}
this.i2x = function(i){
return (i % this.width)
}
this.i2y = function(i){
return ((i - (i % this.width))/ this.width)
}
this.xy2i = function(x,y){
return (y * this.width) + (x)
}
}
Thanks in advance for a solution of this problem
Upvotes: 1
Views: 932
Reputation: 54089
Nearest pixel will result in some zoomed pixels being larger than otheres
It is a problem with the value of scaleval. It has a step of 0.25 and when you calculate each zoomed pixels address you use (and I am guessing as your code has syntax errors) Math.round(xpos * scaleval)
but then you draw the pixel using only the fractional size eg 2.75 not the integer size eg 3.0
The size of each pixel is var xSize = Math.round((xpos + 1) * scaleval)-Math.round(xpos * scaleval)
same for y. That way when the pixel zoom is not an integer value every so many zoomed pixels will be one pixel wider and higher.
The following is a fix of your code but as you had a number of syntax errors and bugs I have had to guess some of your intentions.
xpos = obj.i2x(i)
ypos = obj.i2y(i)
xnow = Math.round(xpos * scaleval)
ynow = Math.round(ypos * scaleval)
// pixel width and height
var pw = Math.round((xpos + 1) * scaleval) - xnow;
var ph = Math.round((ypos + 1) * scaleval) - ynow;
if (xnow < objW && ynow < objH) {
for (var y = 0; y < ph; y++) {
for (var x =0; x < pw; x++) {
var idxnow = obj.xy2i(xnow + x, ynow + y)
tesbackup[idxnow][0] = tes[i][0]
tesbackup[idxnow][1] = tes[i][1]
tesbackup[idxnow][2] = tes[i][2]
}
}
}
}
But you are not really doing a nearest neighbor algorithm. For that you iterate each of the destination pixels finding the nearest pixel and using its colour. That allows you to easily apply a transform to the zoom but still get every pixel and not skip pixels due to rounding errors.
Example of using nearest neighbor lookup for a scale rotated and translated image
var scaleFac = 2.3; // scale 1> zoom in
var panX = 10; // scaled image pan
var panY = 10;
var ang = 1;
var w = ctx.canvas.width; // source image
var h = ctx.canvas.height;
var wd = ctx1.canvas.width; // destination image
var hd = ctx1.canvas.height;
// use 32bit ints as we are not interested in the channels
var src = ctx.getImageData(0, 0, w, h);
var data = new Uint32Array(src.data.buffer);// source
var dest = ctx1.createImageData(wd, hd);
var zoomData = new Uint32Array(dest.data.buffer);// destination
var xdx = Math.cos(ang) * scaleFac; // xAxis vector x
var xdy = Math.sin(ang) * scaleFac; // xAxis vector y
var ind = 0;
var xx,yy;
for(var y = 0; y < hd; y ++){
for(var x = 0; x < wd; x ++){
// transform point
xx = (x * xdx - y * xdy + panX);
yy = (x * xdy + y * xdx + panY);
// is the lookup pixel in bounds
if(xx >= 0 && xx < w && yy >= 0 && yy < h){
// use the nearest pixel to set the new pixel
zoomData[ind++] = data[(xx | 0) + (yy | 0) * w]; // set the pixel
}else{
zoomData[ind++] = 0; // pixels outside bound are transparent
}
}
}
ctx1.putImageData(dest, 0, 0); // put the pixels onto the destination canvas
Upvotes: 1