Rotating a 1d RGBA array - javascript

I'm working with a 1D pixel RGBA array that looks like this:
pixelArray =[0,0,0,255,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255];
What this pixelArray corresponds to when drawn is 2 black pixels and 4 white pixels:
BB
WW
WW
My goal is to rotate the order of the pixels inside the array so the picture when drawn will look like
BWW or WWB
BWW WWB
Which means I need to convert the pixelArray to
rotatedPixelArray = [0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,255,255,255,255,255,255,255,255,255]
The above example is just that, an example. The actual rgba could represent any image and could have a length of 1 million +.
I've tried various algorithms like this, and converting to a 2d array and then rotating and flattening (which does work) but i'd like to avoid this because speed/memory is an issue.

Maybe this could be helpful in p5.js for 90 degrees rotation (left and right) (the previous suggested solution seems to have a little 'asymmetric' error):
function rotateRight(img){
var w = img.width;
var h = img.height;
var index, indexr;
var img2 = createImage(w, h);
img.loadPixels();
img2.loadPixels();
indexr = 0;
for (let x = 0; x < w; x++) {
for(let y = h - 1; y >= 0; y--) {
index = (x+y*w)*4;
img2.pixels[indexr] = img.pixels[index];
img2.pixels[indexr + 1] = img.pixels[index+1];
img2.pixels[indexr + 2] = img.pixels[index+2];
img2.pixels[indexr + 3] = img.pixels[index+3];
indexr += 4;
}
}
img.updatePixels();
img2.updatePixels();
return img2;
}
function rotateLeft(img){
var w = img.width;
var h = img.height;
var index, indexr;
var img2 = createImage(w, h);
img.loadPixels();
img2.loadPixels();
indexr = 0;
for (let x = w - 1; x >= 0; x--) {
for(let y = 0; y < h; y++) {
index = (x+y*w)*4;
img2.pixels[indexr] = img.pixels[index];
img2.pixels[indexr + 1] = img.pixels[index+1];
img2.pixels[indexr + 2] = img.pixels[index+2];
img2.pixels[indexr + 3] = img.pixels[index+3];
indexr += 4;
}
}
img.updatePixels();
img2.updatePixels();
return img2;
}

So I figured it out, in my case it needed to be rotated left or right. The code I used is as followed:
function rotatePixelArray(pixelArray,w,h) {
var rotatedArray = [];
for (var x=0;x<w;x++) {
for(var y=0;y<h;y++) {
index = (x+y*w)*4;
rotatedArray.push(pixelArray[index]);
rotatedArray.push(pixelArray[index+1]);
rotatedArray.push(pixelArray[index+2]);
rotatedArray.push(pixelArray[index+3]);
}
}
return rotatedArray;
}
To rotate it back you can pass in switched w,h variables.

Related

print image using ESC POS commands Javascript

I'm trying print image date using ESC POS commands in Javascript, but until now just crap is printing out.
Command ESC *
I'm using Javascript, trying print direct to bluethoo a image in bit64string.
I tried this example with ESC #, ESC * command in Javscript
It only prints me characters, but not the image
Thank in advance
// let image = context.getImageData(0, 0, width, height)
getImageData(image, Width, Height){
var dimensions = Width * Height
var dots = new Uint8Array(dimensions)
var index = 0
var threshold = 127;
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
var color = this.getPixelI(image, x, y)
//let luminance
let luminance = (0.2126* color[0] + 0.7152 * color[1] + 0.0722 * color[2]);
dots[index] = luminance < threshold
index++;
}
}
return dots
}
getPixelI(imgData, x, y) {
var i = y * (imgData.width * 4) + x * 4;
var d = imgData.data;
return [d[i],d[i+1],d[i+2], d[i+3]] // returns array [R,G,B,A]
}

Strange problems with Javascript "multi-dimensional arrays" ( ie. arrays of arrays )

I have been working on a project in Javascript and after a certain development stage, the code stopped working. I've narrowed the problem down to creating and indexing "multi-dimensional" arrays in Javascript. I've included code just to test creating arrays of arrays, assigning color values to the arrays, and then a test if the values can be displayed on-screen via a loop that iterates through the "multi-dimensional array".
I've tried just about every method of creating arrays of arrays in Javascript -- some code snippets from answers even here -- but nothing has worked.
function setRGB(image, width1, x1, y1, r, g, b, a) {
var t1 = y1 * width1 * 4 + x1 * 4;
image.data[t1] = r;
image.data[t1 + 1] = g;
image.data[t1 + 2] = b;
image.data[t1 + 3] = a;
}
function draw() {
var pixels = [];
for (var i = 0; i < height; ++i) {
var row = [];
for (var j = 0; j < width; ++j) {
row[j] = 0;
}
pixels[i] = row;
}
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
var canvas = document.getElementById('test');
var width = 500;
var height = 500;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var canvasImage = ctx.getImageData(0, 0, width, height);
var testImage = ctx.createImageData(width, height);
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
pixels[y][x] = Math.round(Math.random() * 255);
}
}
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
console.log(pixels[y][x].toString());
}
}
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
setRGB(testImage, width, pixels[y][x], pixels[y][x], pixels[y][x], 255);
ctx.putImageData(testImage, 0, 0);
canvasImage = testImage;
ctx.putImageData(canvasImage, 0, 0);
}
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Array test</title>
</head>
<body onload="draw();">
<canvas id="test">Sorry, but your browser doesn't support the 'canvas' element.</canvas>
</body>
</html>
In Google Chrome, nothing is displayed on-screen and the web console shows the error :
array-plot-test-1.html:26 Uncaught TypeError: Cannot read property '29' of undefined
at draw (array-plot-test-1.html:26)
at onload (array-plot-test-1.html:60)
even though the array of arrays is indexed in its creation loop.
The first time the array is indexed outside its creation loop consists of the test code :
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
In Mozilla Firefox, I get the error :
TypeError: pixels[12] is undefined[Learn More] array-plot-test-1.html:26:3
draw file:///[censored]/[censored]/[censored]/array-plot-test-1.html:26
onload file:///[censored]/[censored]/[censored]/array-plot-test-1.html:1
I've rewritten the array test code about a dozen different times, at least, using "solutions" provided by sites such as Stack Overflow, but nothing has worked so far. I expect there is some basic error that I've made.
Curiously, if I create a "multi-dimensional array" in Javascript like so
array1 = [];
for (var i=0; i < max; ++i) {
var temp = [1, 2, 3];
array[i] = temp;
}
with a pre-defined array, then Javascript "multi-dimensional arrays" work fine, but I need arrays of arrays of arbitrary size which is why I can't use the above code snippet.
You defined width and height after using them. You need to define them before using them.
You also were not passing x and y to setRGB
I suggest you learn how to use the debugger in your browser.
Not sure why you had this
ctx.putImageData(testImage, 0, 0);
canvasImage = testImage;
ctx.putImageData(canvasImage, 0, 0)
inside your loop. You’re drawing the same thing twice plus your drawing both of those 500x500 times. You won’t see the result until your draw function exits so there’s no reason to do that inside the loop. Just moved it after the loop.
Also, put
“use strict”;
at the top of your JavaScript and never use var, use const and let as they will help you find these kinds of issues.
function setRGB(image, width1, x1, y1, r, g, b, a) {
var t1 = y1 * width1 * 4 + x1 * 4;
image.data[t1] = r;
image.data[t1 + 1] = g;
image.data[t1 + 2] = b;
image.data[t1 + 3] = a;
}
function draw() {
var width = 500;
var height = 500;
var pixels = [];
for (var i = 0; i < height; ++i) {
var row = [];
for (var j = 0; j < width; ++j) {
row[j] = 0;
}
pixels[i] = row;
}
var k = 12;
var j = 29;
console.log(pixels[12][29].toString());
var canvas = document.getElementById('test');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
var testImage = ctx.createImageData(width, height);
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
pixels[y][x] = Math.round(Math.random() * 255);
}
}
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
setRGB(testImage, width, x, y, pixels[y][x], pixels[y][x], pixels[y][x], 255);
}
}
ctx.putImageData(testImage, 0, 0);
}
draw();
<canvas id="test"></canvas>
Seems the problems were caused by some dumb errors including accidentally not noticing that the width and height variables hadn't been declared yet and also my failure to pass setRGB() its x and y values.
Thanks to all who helped :)
jdb2

Canvas image zooming using Nearest Neighbor Algorithm

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
Rounding out pixels
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.
Nearest neighbor
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

How to divide image in tiles?

I have to achieve the following task:
divides the image into tiles, computes the average color of each tile,
fetches a tile from the server for that color, and composites the
results into a photomosaic of the original image.
What would be the best strategy? the first solution coming to my mind is using canvas.
A simple way to get pixel data and finding the means of tiles. The code will need more checks for images that do not have dimensions that can be divided by the number of tiles.
var image = new Image();
image.src = ??? // the URL if the image is not from your domain you will have to move it to your server first
// wait for image to load
image.onload = function(){
// create a canvas
var canvas = document.createElement("canvas");
//set its size to match the image
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d"); // get the 2d interface
// draw the image on the canvas
ctx.drawImage(this,0,0);
// get the tile size
var tileSizeX = Math.floor(this.width / 10);
var tileSizeY = Math.floor(this.height / 10);
var x,y;
// array to hold tile colours
var tileColours = [];
// for each tile
for(y = 0; y < this.height; y += tileSizeY){
for(x = 0; x < this.width; x += tileSizeX){
// get the pixel data
var imgData = ctx.getImageData(x,y,tileSizeX,tileSizeY);
var r,g,b,ind;
var i = tileSizeY * tileSizeX; // get pixel count
ind = r = g = b = 0;
// for each pixel (rgba 8 bits each)
while(i > 0){
// sum the channels
r += imgData.data[ind++];
g += imgData.data[ind++];
b += imgData.data[ind++];
ind ++;
i --;
}
i = ind /4; // get the count again
// calculate channel means
r /= i;
g /= i;
b /= i;
//store the tile coords and colour
tileColours[tileColours.length] = {
rgb : [r,g,b],
x : x,
y : y,
}
}
// all done now fetch the images for the found tiles.
}
I created a solution for this (I am not getting the tile images from back end)
// first function call to create photomosaic
function photomosaic(image) {
// Dimensions of each tile
var tileWidth = TILE_WIDTH;
var tileHeight = TILE_HEIGHT;
//creating the canvas for photomosaic
var canvas = document.createElement('canvas');
var context = canvas.getContext("2d");
canvas.height = image.height;
canvas.width = image.width;
var imageData = context.getImageData(0, 0, image.width, image.height);
var pixels = imageData.data;
// Number of mosaic tiles
var numTileRows = image.width / tileWidth;
var numTileCols = image.height / tileHeight;
//canvas copy of image
var imageCanvas = document.createElement('canvas');
var imageCanvasContext = canvas.getContext('2d');
imageCanvas.height = image.height;
imageCanvas.width = image.width;
imageCanvasContext.drawImage(image, 0, 0);
//function for finding the average color
function averageColor(row, column) {
var blockSize = 1, // we can set how many pixels to skip
data, width, height,
i = -4,
length,
rgb = {
r: 0,
g: 0,
b: 0
},
count = 0;
try {
data = imageCanvasContext.getImageData(column * TILE_WIDTH, row * TILE_HEIGHT, TILE_HEIGHT, TILE_WIDTH);
} catch (e) {
alert('Not happening this time!');
return rgb;
}
length = data.data.length;
while ((i += blockSize * 4) < length) {
++count;
rgb.r += data.data[i];
rgb.g += data.data[i + 1];
rgb.b += data.data[i + 2];
}
// ~~ used to floor values
rgb.r = ~~(rgb.r / count);
rgb.g = ~~(rgb.g / count);
rgb.b = ~~(rgb.b / count);
return rgb;
}
// Loop through each tile
for (var r = 0; r < numTileRows; r++) {
for (var c = 0; c < numTileCols; c++) {
// Set the pixel values for each tile
var rgb = averageColor(r, c)
var red = rgb.r;
var green = rgb.g;
var blue = rgb.b;
// Loop through each tile pixel
for (var tr = 0; tr < tileHeight; tr++) {
for (var tc = 0; tc < tileWidth; tc++) {
// Calculate the true position of the tile pixel
var trueRow = (r * tileHeight) + tr;
var trueCol = (c * tileWidth) + tc;
// Calculate the position of the current pixel in the array
var pos = (trueRow * (imageData.width * 4)) + (trueCol * 4);
// Assign the colour to each pixel
pixels[pos + 0] = red;
pixels[pos + 1] = green;
pixels[pos + 2] = blue;
pixels[pos + 3] = 255;
};
};
};
};
// Draw image data to the canvas
context.putImageData(imageData, 0, 0);
return canvas;
}
function create() {
var image = document.getElementById('image');
var canvas = photomosaic(image);
document.getElementById("output").appendChild(canvas);
};
DEMO:https://jsfiddle.net/gurinderiitr/sx735L5n/
Try using the JIMP javascript library to read the pixel color and use invert, normalize or similar property for modifying the image.
Have a look on the jimp library
https://github.com/oliver-moran/jimp

index a pixel using one-loop or two-loops

I saw some user index a pixel in [Image Data] array, with the following two methods:
for(var i = 0; i < imageData.length; i+=4) {
data[i] = r;
data[i+1] = g;
data[i+2] = b;
data[i+3] = a;
}
or with this method.
for(var x = 0; w < canvas.width; x++) {
for(var y = 0; h < canvas.height; y++) {
var index = (x + y*canvas.width)*4;
}
}
So, I want to know is there any difference between the two. Also, if the both are same, then which is fastest.
It all depends of your needs :
• If you need to iterate through all pixel linearly, just do :
var pixelCount=data.length, i=0;
while (pixelCount--) {
data[i++] = r;
data[i++] = g;
data[i++] = b;
data[i++] = a;
}
• If you iterate though all pixels, but need the (x,y) of each points to perform some computations do:
var index=0, canvasWidth = canvas.width, canvasHeight = canvas.height ;
for(var y = 0; h < canvasHeight; y++) {
for(var x = 0; w < canvasWidth ; x++) {
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
data[index++] = /* depends on x, y */;
}
}
(it's especially important to cache canvas.width/height to avoid DOM access within a loop).
• If you iterate through a rectangle within the data, then you can't avoid computing the index, which you can speed up a bit by using bit shifting :
var startX = ?? , startY = ???, endX = ???, endY = ??? ;
var canvasWidth = canvas.width;
var index=0;
for(var y = startY; y <= endY; y++) {
for(var x = startX; x <= endX ; x++) {
index = ( y * canvasWidth + x ) << 2 ;
data[index] = ... ;
data[index+1] = ... ;
data[index+2] = ... ;
data[index+3] = ... ;
}
}
Both of these methods will produce the same results. I assume that the index placement is the same in both images once they are calculated. The only thing that changes is the order that pixels are changed in.
For speed, the second one is likely to be slower. First of all, this is because of caching speed, programs can access data in similar array locations faster than continuously accessing locations across the array. Additionally, the compiler has to do a few multiplication operations and an addition in order to recalculate the index, instead of just an addition. For faster speed on the second one, try switching the x and y for loops, or multiply x by canvas.height instead of y by canvas.width.
The previous answer work but I think I can provide a single loop answer.
Imagine we have to pick the pixels in a square image of 100x100:
const size = 100;
const pixels = size * size;
for( let index = 0; index < pixels; index++ ){
const id = ~~index;
const x = id % size;
const y = ~~(id / size);
console.log("x:", x, "y:", y);
}
Open your dev-tools to see the full console output.

Categories

Resources