Dashed border seems like its multiplying - node.js

I'm makking a Node.js app with Jimp. I have a function for selecting part of an image. It draws a rectangle in the area, writes the number of the selection in the middle and then draws a dashed border around the outside of the rectangle selection. The dashed border seems to be multiplying and I have no idea why. Here's the full code of the file:
imageManipulationUtil.js:
const Jimp = require("jimp");
module.exports = async (readPath, writePath, comments, callback) => {
const originalImage = await Jimp.read(readPath);
const font = await Jimp.loadFont(Jimp.FONT_SANS_32_BLACK);
// Please ignore this part. Focus on the other part (the functions I mentioned above).
const addedWidth = 500;
const commentsHeight = comments.reduce((commentsHeight, { comment }, i) => {
comments[i].comment = `${i + 1}. ${comment}`;
const textWidth = Jimp.measureText(font, comment);
const textHeight = Jimp.measureTextHeight(font, comment);
const lines = Math.ceil(textWidth / addedWidth);
const height = textHeight * lines;
return commentsHeight + height;
}, 0);
const imageHeight = commentsHeight + 10;
if (imageHeight > originalImage.getHeight())
originalImage.resize(Jimp.AUTO, imageHeight);
const newImage = new Jimp(
originalImage.getWidth() + addedWidth,
originalImage.getHeight(),
0xffffffff
);
// Some other code for another purpose
// !!! Important code START !!!
drawSelectionRects(comments, font, newImage);
async function drawSelectionRects(comments, font, image) {
comments.forEach(({ dimensions }) => {
image.scanQuiet(
dimensions.x,
dimensions.y,
dimensions.width,
dimensions.height,
(x, y, idx) => {
const color = {
r: image.bitmap.data[idx + 0],
g: image.bitmap.data[idx + 1],
b: image.bitmap.data[idx + 2],
a: image.bitmap.data[idx + 3] / 255,
};
const selectionColor = {
r: 187,
g: 187,
b: 187,
a: 187,
};
const newColor = blendColors(color, selectionColor);
const hexColor = Jimp.rgbaToInt(
newColor.r,
newColor.g,
newColor.b,
255
);
image.setPixelColor(hexColor, x, y);
}
);
dashedBorder(
image,
{ lineDash: [20, 5], lineWidth: 3, color: 0x1a53ffbb },
dimensions
);
});
comments.forEach(({ dimensions }, i) => {
const text = `${i + 1}`;
let textX =
dimensions.x + (dimensions.width - Jimp.measureText(font, text)) / 2;
let textY =
dimensions.y +
(dimensions.height - Jimp.measureTextHeight(font, text)) / 2;
image.print(font, textX, textY, text);
});
}
function blendColors(c1, c2) {
const stepPoint = c2.a / 255;
const r = c1.r + stepPoint * (c2.r - c1.r);
const g = c1.g + stepPoint * (c2.g - c1.g);
const b = c1.b + stepPoint * (c2.b - c1.b);
return { r, g, b };
}
function dashedBorder(
image,
{ lineDash, lineWidth, color },
{ x, y, width, height }
) {
let drawing = true,
passed = 0;
color = Jimp.intToRGBA(color);
// Top border
for (let i = x; i < x + width; i++) {
if (drawing) {
const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, y));
const newColor = blendColors(pixelColor, color);
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
i,
y - k
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
drawing = true;
// Right border
for (let j = y; j < y + height; j++) {
if (drawing) {
const pixelColor = Jimp.intToRGBA(image.getPixelColor(x + width, y));
const newColor = blendColors(pixelColor, color);
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
x + width + k,
j
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
drawing = true;
// Bottom border
for (let i = x + width; i > x; i--) {
if (drawing) {
const pixelColor = Jimp.intToRGBA(image.getPixelColor(i, y + height));
const newColor = blendColors(pixelColor, color);
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
i,
y + height + k
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
drawing = true;
// Left border
for (let j = y + height; j > y + lineWidth; j--) {
if (drawing) {
const pixelColor = Jimp.intToRGBA(image.getPixelColor(x, j));
const newColor = blendColors(pixelColor, color);
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(newColor.r, newColor.g, newColor.b, 255),
x - k,
j
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
}
newImage.write(writePath);
callback();
};
// !!! Important code END !!!
The code exports a function that takes some parameters. The most important parameter is the comments parameter (specifically the dimensions of the comment). The comments param is an array of objects. The objects have a dimensions key which is the important one in this question.
The image uploaded:
Image recieved:
The problem that makes me think the image is multiplying is that in some places the border is more transparent than in other parts. Originally I thought that was just because of the image I used but then I switched it and noticed it was a real problem.
New full code:
const Jimp = require("jimp");
function dashedBorder(
image,
{ lineDash, lineWidth, color },
{ x, y, width, height }
) {
let drawing = true,
passed = 0,
outsideWidth = lineWidth - 1;
color = Jimp.intToRGBA(color);
// Top border
for (let i = x - outsideWidth; i < x + width + outsideWidth; i++) {
if (drawing) {
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(color.r, color.g, color.b, 255),
i,
y - k
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
// Right border
for (
let j = y + lineWidth - outsideWidth;
j < y + height - (lineWidth - outsideWidth);
j++
) {
if (drawing) {
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(color.r, color.g, color.b, 255),
x + width + k - 1,
j
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
// Bottom border
for (let i = x + width + lineWidth - outsideWidth; i > x - lineWidth; i--) {
if (drawing) {
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(color.r, color.g, color.b, 255),
i,
y + height + k - 1
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
// Left border
for (let j = y + height - outsideWidth; j > y; j--) {
if (drawing) {
for (let k = 0; k < lineWidth; k++) {
image.setPixelColor(
Jimp.rgbaToInt(color.r, color.g, color.b, 255),
x - k,
j
);
}
}
passed++;
if (
(passed >= lineDash[0] && drawing) ||
(passed >= lineDash[1] && !drawing)
) {
drawing = !drawing;
passed = 0;
}
}
}
(async () => {
let image = await Jimp.read("./test.png");
dashedBorder(
image,
{ lineWidth: 3, lineDash: [20, 5], color: 0x1a53ffbb },
{ x: 0, y: 0, width: image.bitmap.width, height: image.bitmap.height }
);
image.write("./test-border.png");
})();
Image:

Related

handling a view frustum when not facing the object

i have made a view frustum from scratch in javascript. i have been having trouble with objects that the camera is place within and also facing away from.
an example of the problem is below
ive been stuck on this for months with little help, chatgpt recommended that i switch to a right handed corodinate system, so i did, but it didnt seem to fix the problem.
my proccess for putting pixels on the screen is described below
step 1 = cross product with camera matrix
step 2 = cross product with projection matrix
step 3 = divide co-ordinates by their own w co-ord (normalize w to 1)
step 3.5 = im currently skipping culling. culling would go here, but since my object is made of multiple vertices i cant cull a vertex just because its offscreen as it forms part of a whole object, and that would deform the rest of the object
step 4 = cross product with projection to screen matrix
below shows how the co-ordinates of 1 vertex changes with each step
co-ordinates of vertex before projection
[5.00,9.00,10.00,1.00]
good projection (tile is good)
step 1 = [5, 9, -4.799999999999979, 10]
step 2 = [-8.660254038143698, 15.588457268658656, 15.353535353535376, 4.799999999999979]
step 3 = [-1.8042195912799448, 3.2475952643039006, 3.198653198653217, 1]
step 4 = [-120.63293869199174, 637.1392896455851, 3.198653198653217, 1]
pixels = -120.63293869199174,637.1392896455851
#############################
bad projection from within the object (tile is deformed)
step 1 = [5, 9, 0.6000000000000014, 10]
step 2 = [-8.660254038143698, 15.588457268658656, 20.808080808080813, -0.6000000000000014]
step 3 = [14.433756730239462, -25.980762114431034, -34.68013468013461, 1]
step 4 = [2315.0635095359194, -3747.114317164655, -34.68013468013461, 1]
pixels = 2315.0635095359194,-3747.114317164655
###############################
bad projection from behind (tile appears on the ceiling, when A it should be on the floor and B it shouldn't be visible)
step 1 = [5, 9, 16.800000000000004, 10]
step 2 = [-8.660254038143698, 15.588457268658656, 37.17171717171718, -16.800000000000004]
step 3 = [0.5154913117942677, -0.9278843612296817, -2.2126022126022122, 1]
step 4 = [227.32369676914016, 10.817345815547753, -2.2126022126022122, 1]
pixels = 227.32369676914016, 10.817345815547753
does anyone know which step could be wrong or need changing in situation 2 and 3? and why?
below is a minimal (i know its 500 lines... but its about as minimal as i can get it) just open it in a browser and use wasd to control it.
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/11.3.3/math.js"></script>
<script async src="https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js"></script>
</head>
<body>
<div id="canvas div" style = "position: relative; left: 0px; float:left; top: 0px;" >
<h1> first person below </h1>
<canvas id="mi_canvas" width="300" height="300" style="border-style: solid;"></canvas> <br>
<h1> radar below </h1>
<canvas id="radar_canvas" width="300" height="300" style="border-style: solid;"></canvas>
</div>
<div id="big info div" style = "position: relative; left: 310px; float:left; float:top; top: 0px; width:400px;" >
<div id = "info_1111"> </div><br>
</div>
<script>
var floor_y_pos = 9
canvas = document.getElementById("mi_canvas");
ctx = canvas.getContext("2d");
radar = document.getElementById("radar_canvas");
radar_ctx = radar.getContext("2d");
render_distance = 1000;
fov = math.pi / 2
class Projection{
constructor(){
var NEAR = player.near_plane
var FAR = player.far_plane
var RIGHT = Math.tan(player.h_fov/2)
var LEFT = - RIGHT
var TOP = Math.tan(player.v_fov /2)
var BOTTOM = -TOP
var m00 = 2*NEAR / (RIGHT - LEFT)
var m02 = (RIGHT + LEFT)/(RIGHT - LEFT)
var m11 = 2*NEAR / (TOP - BOTTOM)
var m12 = (TOP + BOTTOM) /(TOP - BOTTOM)
var m22 = (FAR * NEAR) / (FAR - NEAR)
var m23 = -2 * NEAR * FAR / (FAR-NEAR)
this.projection_matrix = [
[-m00,0,m02,0],
[0,m11,0,0],
[m02,m12,-m22,-1],
[0,0,m23,0]
]
var HW=player.H_WIDTH
var HH = player.H_HEIGHT
this.to_screen_matrix = [
[HW,0,0,0],
[0,HH,0,0],
[0,0,1,0],
[HW,HH,0,1]
]
}
}
function multiply(a, b) {
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows); // initialize array of rows
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols); // initialize the current row
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0; // initialize the current cell
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}
function mi_position_matrix_multiplier(A, B)
{
var new_matrix = []
for (var new_num_ind = 0; new_num_ind < A.length; ++new_num_ind)
{
this_num = 0;
for (var a_ind = 0; a_ind < A.length; ++a_ind)
{
this_num += (A[a_ind] * B[a_ind][new_num_ind])
}
// console.log("just added this num to my new matrix = "+this_num.toString())
new_matrix.push(this_num)
}
return new_matrix;
}
function pythagoras(thing1, thing2)
{
dist = (((thing1[0]-thing2[0])**2)+((thing1[1]-thing2[1])**2))**0.5
return dist
}
class vertex{
constructor(x, y,z , id){
this.id = id
this.position = [x,y,z,1]
this.min_dist = 1.5 // minimum possible distance between player and object
}
is_this_object_behind_player(){
var arrow_length = 0.0001;
var pointing_position = [player.position[0]+(player.forward[0]*arrow_length) , player.position[2]-(player.forward[2]*arrow_length)]
var dist1 = pythagoras([this.position[0],this.position[2]], pointing_position)
var dist2 = pythagoras([this.position[0],this.position[2]], [player.position[0],player.position[2]])
if (dist1 < dist2){
return true;}
else if (dist1 > dist2){
return false;}
else{console.log(" else ");}
}
screen_projection(){
var position = mi_position_matrix_multiplier(this.position , player.camera_matrix())
console.log(position+" = position , which is a cross product of this.position"+this.position+" & "+ player.camera_matrix()+ " = player.camera_matrix()")
update_matrix_info_debug("camera_matrix",player.camera_matrix())
update_matrix_info_debug("position", position)
position = mi_position_matrix_multiplier(position , projection.projection_matrix) // does this just convert the position to cameras reference frame.
console.log(position+" = position , which is a cross product of position"+position+" & "+ projection.projection_matrix+ " = projection.projection_matrix")
update_matrix_info_debug("projection_matrix",projection.projection_matrix)
update_matrix_info_debug("position after being multiplied by proj matrix", position)
// if so then i image to screen matrix is insufficient
for (let i = 0; i < position.length; i++) {
position[i] = position[i]/position[3]
}
console.log(position+" = position after being normaslized")
for (let i = 0; i < position.length; i++) {
if (i != 9787781){
console.log(i+" =-= "+position[i])
if (this.is_this_object_behind_player()){for (let ii = 0; ii < position.length; ii++) {position[ii] = -999999999;} console.log("culling1")}
if (position[i] > 2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling2")}
if (position[i] < -2){for (let ii = 0; ii < position.length; ii++) {position[ii] = -9999;} console.log("culling3")}
}
} // also all examples say set position = 0 if culling
console.log(position+" = position after being culled")
position = mi_position_matrix_multiplier(position , projection.to_screen_matrix)
console.log(position+" = position after being multiplied by "+projection.to_screen_matrix+ " = projection.to_screen_matrix")
update_matrix_info_debug("projection.to_screen_matrix",projection.to_screen_matrix)
update_matrix_info_debug("position after being multiplied by projection.to_screen_matrix", position)
ctx.beginPath();
var scale_multiplier = (render_distance / pythagoras([this.position[0],this.position[2]] , [player.position[0],player.position[2]]))*1.5
var arrow_size = 0.02 * scale_multiplier;
ctx.moveTo(position[0]-arrow_size ,position[1]+arrow_size);
ctx.lineTo(position[0]+arrow_size ,position[1]-arrow_size);
ctx.moveTo(position[0]+arrow_size ,position[1]+arrow_size);
ctx.lineTo(position[0]-arrow_size, position[1]-arrow_size);
ctx.stroke();
}
return_screen_projection(dont_cull = false){
var position = mi_position_matrix_multiplier(this.position , player.camera_matrix())
position = mi_position_matrix_multiplier(position , projection.projection_matrix) // does this just convert the position to cameras reference frame.
for (let i = 0; i < position.length; i++) {
position[i] = position[i]/position[3]
}
position = mi_position_matrix_multiplier(position , projection.to_screen_matrix)
return [position[0],position[1]]
}
}
class player{
constructor(){
this.position =[0,0,0,1.0]
this.forward = [0,0,1,1]
this.up = [0,1,0,1]
this.right =[1,0,0,1]
this.h_fov = 3.1415926535/3
this.v_fov = this.h_fov * (canvas.height / canvas.width)
this.near_plane = 1
this.far_plane = 100
this.moving_speed = 0.2
this.rotation_speed = 0.1
this.H_WIDTH = canvas.width/2
this.H_HEIGHT = canvas.height/2
this.anglePitch = 0
this.angleYaw = 0
}
set_camera_angle(){
var rotate = multiply(rotate_x(this.anglePitch) , rotate_y(this.angleYaw))
this.forward = [0, 0, 1, 1]
this.up = [0, 1, 0, 1]
this.right = [1, 0, 0, 1]
this.forward = mi_position_matrix_multiplier(this.forward , rotate)
this.right = mi_position_matrix_multiplier(this.right , rotate)
this.up = mi_position_matrix_multiplier(this.up , rotate)
}
camera_yaw(angle){
this.angleYaw += angle}
translate_matrix(self){
var x = this.position[0];
var y = this.position[1];
var z = this.position[2];
var w = this.position[3];
return [
[1,0,0,0],
[0,1,0,1],
[0,0,1,0],
[-x,-y,z, 1]
]}
rotate_matrix(){
var rx = this.right[0]
var ry = this.right[1]
var rz = this.right[2]
var w = this.right[3]
var fx = this.forward[0]
var fy = this.forward[1]
var fz = this.forward[2]
var w = this.forward[3]
var ux = this.up[0]
var uy = this.up[1]
var uz = this.up[2]
var w = this.up[3]
return [
[rx,ux,fx,0],
[ry,uy,fy,0],
[rz,uz,fz,0],
[0,0,0,1]
]
}
camera_matrix(){
return multiply(this.translate_matrix(), this.rotate_matrix());
}
check_min_distance_isnt_overcome_by_this_move(dx, dy){
var can_move = true;
console.log(" zzzzzzzzzzzzz ")
for (let i = 0; i < objects.length; i++) {
var dist=Math.abs(pythagoras([objects[i].position[0], objects[i].position[2]] , [this.position[0], this.position[2]]))
var dist2=Math.abs(pythagoras([objects[i].position[0], objects[i].position[2]] , [this.position[0]+dx, this.position[2]+dy]))
console.log(dist +" ########################### " +dist2)
if ((dist2 < objects[i].min_dist)&(dist > dist2))
{can_move = false; console.log(objects[i].min_dist +" yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy")}
else{console.log(objects[i].min_dist+" can move this is bloody min dist xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx "+dist2);}
}
return can_move;
}
move(event)
{
var key_code = parseInt(event.keyCode)
if (key_code == 37 || key_code == 39 || key_code == 83 || key_code == 87 || key_code == 119|| key_code == 115)
{
var dx = Math.cos(this.angleYaw)*this.moving_speed
var dy = Math.sin(this.angleYaw)*this.moving_speed
console.log("that were moving = dx , dy = "+dx.toString()+" , "+dy.toString())
if ( key_code == 37 || key_code == 87 || key_code == 119) {
if (this.check_min_distance_isnt_overcome_by_this_move(dx, -dy)){
this.position[0] += -dy
this.position[2] += dx
}
}
if (key_code == 39 || key_code == 83 || key_code == 115) {
for (let i = 0; i < this.position.length; i++) {
if (this.check_min_distance_isnt_overcome_by_this_move(dx, dy)){
this.position[0] += dy
this.position[2] += -dx
}
}
}
}
else {
if ( key_code == 38 || key_code == 65 || key_code == 97) {
this.camera_yaw(-this.rotation_speed)
}
if (key_code == 40 || key_code == 68 || key_code == 100) {
this.camera_yaw(this.rotation_speed)
}
this.set_camera_angle()
}
}
}
function translate(pos){
tx,ty,tz=pos
return np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[tx,ty,tz,1]
])}
function rotate_x(angle){
return [
[1,0,0,0],
[0,Math.cos(angle),Math.sin(angle),0],
[0,-Math.sin(angle),Math.cos(angle),0],
[0,0,0,1]
]
}
function rotate_y(a){
return [
[math.cos(a),0, -math.sin(a),0],
[0,1,0,0],
[math.sin(a), 0 , math.cos(a),0],
[0,0,0,1]
]
}
function update_radar(){
var arrow_length = 4;
var object_size = 6.5;
radar_ctx.beginPath();
var mid_screen = [radar.width/2,radar.height/2];
pointing_position = [mid_screen[0]+(player.forward[0]*arrow_length) , mid_screen[1]-(player.forward[2]*arrow_length)]
radar_ctx.moveTo(mid_screen[0], mid_screen[1]); // start of player pos on radar
radar_ctx.lineTo(pointing_position[0], pointing_position[1]);
radar_ctx.lineTo(pointing_position[0]-2, pointing_position[1]-2);
radar_ctx.lineTo(pointing_position[0]+2, pointing_position[1]+2);
radar_ctx.lineTo(pointing_position[0]-2, pointing_position[1]+2);
radar_ctx.moveTo(mid_screen[0], mid_screen[1]);
for (let i = 0; i < objects.length; i++) {
var dx = (player.position[0]-objects[i].position[0])
var dz = player.position[2]+objects[i].position[2]
var x = (dx*2) + mid_screen[0]
var z = (dz*2) + mid_screen[1]
x = x +(object_size/2)
z = z +(object_size/2)
radar_ctx.moveTo(x-object_size,z-object_size);
radar_ctx.lineTo(x+object_size,z+object_size);
radar_ctx.moveTo(x+object_size,z-object_size);
radar_ctx.lineTo(x-object_size,z+object_size);
}
radar_ctx.stroke();
}
function update_matrix_info_debug(matrix_name, matrix){
if (matrix[0].length > 1)
{
for (let x = 1; x < matrix.length+1; x++) {
for (let y = 1; y < matrix.length+1; y++) {
// console.log(matrix_name.toString()+"_"+x.toString()+y.toString());
document.getElementById(matrix_name.toString()+"_"+x.toString()+y.toString()).innerHTML = matrix[x-1][y-1]
}
}
}
else {
for (let x = 1; x < matrix.length+1; x++) {document.getElementById(matrix_name.toString()+"_"+"1"+x.toString()).innerHTML = matrix[x-1]}
}
}
class box{
constructor(x,z,size){
var low_y = 0.5
var high_y = low_y - size
this.position = [x+(size/2),0,z+(size/2)]
this.vertices = [new vertex(x,low_y,z,0),new vertex(x+size,low_y,z,1),new vertex(x,low_y,z+size,2),new vertex(x+size,low_y,z+size,3),
new vertex(x,high_y,z,4),new vertex(x+size,high_y,z,5),new vertex(x,high_y,z+size,6),new vertex(x+size,high_y,z+size,7)
]
this.faces=[ [0,1,3,2,0], [0,1,5,4,0] , [1,3,7,5,1] , [4,5,7,6,4] , [2,6,7,3,2] , [0,4,6,2,0]]
// this.faces=[ [0,4,6,2,0]]
}
draw_all_vertices(){
for (let i = 0; i < this.vertices.length; i++) {
this.vertices[i].screen_projection()
}
}
draw_all_faces(){
var each_point = []
for (let i = 0; i < this.vertices.length; i++) {
each_point.push(this.vertices[i].return_screen_projection())
}
var skip_drawing = if_most_of_these_numbers_are_off_screen(each_point)
if (skip_drawing){console.log(" skipp drawing any faces init ");return;}
ctx.fillStyle = '#f00';
var moved_to_first_yet = false
for (let face = 0; face < this.faces.length; face++) {
ctx.beginPath();
console.log("%%%%%%%%%%%%%%%%%%%%%%%%%");
console.log(this.faces);
console.log(this.faces[face]);
for (let vertex = 0; vertex < this.faces[face].length; vertex++)
{
console.log(vertex+" vertex bef dddddddddddddddddddddddddddddd")
var vertex2 = this.faces[face][vertex]
console.log(vertex2+" vertex aft ddddddddd ")
if (moved_to_first_yet == false)
{
moved_to_first_yet = true
ctx.moveTo( each_point[this.vertices[vertex2].id][0],each_point[this.vertices[vertex2].id][1]);
}
else{ctx.lineTo( each_point[this.vertices[vertex2].id][0],each_point[this.vertices[vertex2].id][1]);}
}
ctx.closePath();
ctx.fill();
}
}
}
class two_d_surdace {
constructor(verex1,verex2,verex3,verex4 , colour){
this.vertices = [verex1,verex2,verex3,verex4]
this.colour = colour
}
draw_all_faces(){
var each_point = []
for (let i = 0; i < this.vertices.length; i++) {
each_point.push(this.vertices[i].return_screen_projection(true))
}
ctx.fillStyle = this.colour;
var moved_to_first_yet = false
for (let vertex = 0; vertex < this.vertices.length; vertex++)
{
console.log(each_point[vertex][0]+" , "+each_point[vertex][1]+ " actual x y points on screen for this vertex of corner of floor ")
if (moved_to_first_yet == false)
{
moved_to_first_yet = true
ctx.moveTo( each_point[vertex][0],each_point[vertex][1]);
}
else{ctx.lineTo( each_point[vertex][0],each_point[vertex][1]);}
}
ctx.closePath();
ctx.fill();
}
}
function if_off_screen(x, y)
{
if (x> canvas.width || x < 0){console.log(x +" x = off screen "); return true;}
if (y > canvas.height || y < 0){console.log(y +" y = off screen "); return true;}
console.log(x +" , "+y + " =x,y = not off screen ");
return false;
}
function if_most_of_these_numbers_are_off_screen(numbers){
var threshold = 1; //Math.floor(numbers.length*0.49)
var counter = 0
console.log(numbers + " xxxx numbers as they come in ")
for (let i = 0; i < numbers.length; i++) { if (if_off_screen(numbers[i][0], numbers[i][1])){console.log(numbers[i]+" , "+numbers[i+1]+ " = numbers[i] are off screen"); counter +=1} else{console.log(numbers[i]+" , "+numbers[i+1]+ " = numbers[i] not off screen")} }
console.log("quuin quoirs of raptor");
if (counter >= threshold){console.log(threshold+" < " + counter);return true}
console.log(threshold+" > " + counter);
return false;
}
player = new player();
projection = new Projection()
objects = [] //
floor = new two_d_surdace(new vertex(50,floor_y_pos,50) , new vertex(-50,floor_y_pos,50) , new vertex(-50,floor_y_pos,-50) , new vertex(50,floor_y_pos,-50) , '#F90' )
update_radar()
$(document).on("keypress", function (event) {
player.move(event)
ctx.beginPath();
radar_ctx.beginPath();
radar_ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < objects.length; i++) {
objects[i].draw_all_faces()
objects[i].draw_all_vertices()
}
floor.draw_all_faces()
update_radar()
});
</script>
</body>

Cs50 edges filter is failing to process corner and edges pixels

In the problem description, we were told to take outer pixels as having a value of "0".
I discriminate de outer pixels so as to do nothing with them, they won´t add a value, because multiplying by 0 gets 0.
I can´t seem to find where i went wrong. Can somebody give me a clue. Thanks a lot.
here is my code
// Detect edges
void edges(int height, int width, RGBTRIPLE image[height][width])
{
// Make copy
RGBTRIPLE copy[height][width];
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
copy[i][j] = image[i][j];
}
}
double gxB, gxR, gxG, gyR, gyB, gyG;
int GX[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int GY[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
// Loop through pixels
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
gxB = 0;
gxR = 0;
gxG = 0;
gyR = 0;
gyB = 0;
gyG = 0;
// Loop through 3x3 box
for (int row = (i - 1); row < (i + 2); row++)
{
for (int col = (j - 1); col < (j +2); col++)
{
// Discriminate border pixels
if (exists(row, col, height, width))
{
gxR += copy[row][col].rgbtRed * GX[row][col];
gxG += copy[row][col].rgbtGreen * GX[row][col];
gxB += copy[row][col].rgbtBlue * GX[row][col];
gyR += copy[row][col].rgbtRed * GY[row][col];
gyG += copy[row][col].rgbtGreen * GY[row][col];
gyB += copy[row][col].rgbtBlue * GY[row][col];
}
else
{
gxR += 0.0;
gxG += 0.0;
gxB += 0.0;
gyR += 0.0;
gyG += 0.0;
gyB += 0.0;
}
}
}
image[i][j].rgbtRed = trimm(round(sqrt((gxR * gxR) + (gyR * gyR))));
image[i][j].rgbtGreen = trimm(round(sqrt((gxG * gxG) + (gyG * gyG))));
image[i][j].rgbtBlue = trimm(round(sqrt((gxB * gxB) + (gyB * gyB))));
}
}
return;
}
bool exists(int ROW, int COL, int H, int W)
{
if (((ROW >= 0) && (ROW < H)) && ((COL >= 0) && (COL < W)))
{
return true;
}
else
{
return false;
}
}
int trimm(int x)
{
if (x < 256)
{
return x;
}
else
{
return 255;
}
}

How can you get a sprite to follow another sprite in Java?

Here is the code for the initial character:
public class Player extends Mob {
private InputHandler input;
private int colour = Colours.get(-1, 111, 145, 543);
private int scale = 1;
protected boolean isSwimming = false;
private int tickCount = 0;
public Player(Level level, int x, int y, InputHandler input) {
super(level, "Player", x, y, 1);
this.input = input;
}
public void tick() {
int xa = 0;
int ya = 0;
if (input.up.isPressed()) {
ya--;
}
if (input.down.isPressed()) {
ya++;
}
if (input.left.isPressed()) {
xa--;
}
if (input.right.isPressed()) {
xa++;
}
if (xa != 0 || ya != 0) {
move(xa, ya);
isMoving = true;
} else {
isMoving = false;
}
if (level.getTile(this.x >> 3, this.y >> 3).getId() == 3) {
isSwimming = true;
}
if (isSwimming && level.getTile(this.x >> 3, this.y >> 3).getId() != 3) {
isSwimming = false;
}
tickCount++;
this.scale = 1;
}
public void render(Screen screen) {
int xTile = 0;
int yTile = 28;
int walkingSpeed = 4;
int flipTop = (numSteps >> walkingSpeed) & 1;
int flipBottom = (numSteps >> walkingSpeed) & 1;
if (movingDir == 1) {
xTile += 2;
} else if (movingDir > 1) {
xTile += 4 + ((numSteps >> walkingSpeed) & 1) * 2;
flipTop = (movingDir - 1) % 2;
}
int modifier = 8 * scale;
int xOffset = x - modifier / 2;
int yOffset = y - modifier / 2 - 4;
if (isSwimming) {
int waterColour = 0;
yOffset += 4;
if (tickCount % 60 < 15) {
waterColour = Colours.get(-1, -1, 225, -1);
} else if (15 <= tickCount % 60 && tickCount % 60 < 30) {
yOffset -= 1;
waterColour = Colours.get(-1, 225, 115, -1);
} else if (30 <= tickCount % 60 && tickCount % 60 < 45) {
waterColour = Colours.get(-1, 115, -1, 225);
} else {
waterColour = Colours.get(-1, 225, 115, -1);
}
screen.render(xOffset, yOffset + 3, 0 + 27 * 32, waterColour, 0x00, 1);
screen.render(xOffset + 8, yOffset + 3, 0 + 27 * 32, waterColour, 0x01, 1);
}
// upper body
screen.render(xOffset + (modifier * flipTop), yOffset, xTile + yTile * 32, colour, flipTop, scale);
screen.render(xOffset + modifier - (modifier * flipTop), yOffset, (xTile + 1) + yTile * 32, colour, flipTop,
scale);
if (!isSwimming) {
// lower body
screen.render(xOffset + (modifier * flipBottom), yOffset + modifier, xTile + (yTile + 1) * 32, colour,
flipBottom, scale);
screen.render(xOffset + modifier - (modifier * flipBottom), yOffset + modifier,
(xTile + 1) + (yTile + 1) * 32, colour, flipBottom, scale);
}
}
public boolean hasCollided(int xa, int ya) {
int xMin = 0;
int xMax = 7;
int yMin = 1;
int yMax = 7;
for (int x = xMin; x < xMax; x++) {
if (isSolidTile(xa, ya, x, yMin)) {
return true;
}
}
for (int x = xMin; x < xMax; x++) {
if (isSolidTile(xa, ya, x, yMax)) {
return true;
}
}
for (int y = yMin; y < yMax; y++) {
if (isSolidTile(xa, ya, xMin, y)) {
return true;
}
}
for (int y = yMin; y < yMax; y++) {
if (isSolidTile(xa, ya, xMax, y)) {
return true;
}
}
return false;
}
}
can get another character to load at a given location; however, I cannot figure out an algorithm to have it follow the initial player. In other words the second one will follow these same collision and commands. Any help understanding is appreciated, thank you!

Fabric custom object

fabric.ThreePointArc = fabric.util.createClass(fabric.Circle, {
type: 'threePointArc',
points: [], //array of startPoint, intermediatePoint, endPoint
arcCenter: new fabric.Point(null, null),
arcBounds: null,
radius: null,
initialize: function (points, options) {
if (!points || points.length === 0) {
return;
}
this.points = points;
this.callSuper('initialize', options);
// supports only originX and originY as center
this.originX = this.originY = 'center';
},
_set: function(key, value) {
this.callSuper('_set', key, value);
if (key === 'points') {
this._calcArcCenter();
this._calcDimensions();
this.setCoords();
}
return this;
},
setRadius: function(value) {
this.radius = value;
return this;
},
_calcDimensions: function() {
this._calcArcAngles();
this.setRadius(cMath.getLength(this.arcCenter, this.points[0]));
this._calcArcBounds();
this.width = this.arcBounds.width;
this.height = this.arcBounds.height;
this.top = this.arcBounds.y + this.arcBounds.height / 2;
this.left = this.arcBounds.x + this.arcBounds.width / 2;
},
_calcArcCenter: function() {
var c1Mp = cMath.getMidPoint(this.points[0], this.points[1]), // chord 1 midpoint
c2Mp = cMath.getMidPoint(this.points[1], this.points[2]), // chord 2 midpoint
c1pm = -(1 / cMath.getSlope(this.points[0], this.points[1])), // chord 1 perpendicular bisector slope
c2pm = -(1 / cMath.getSlope(this.points[1], this.points[2])); // chord 2 perpendicular bisector slope
// chord perpendicular bisectors meet at the center
this.arcCenter.x = (c2Mp.y - (c2pm * c2Mp.x) + (c1pm * c1Mp.x) - c1Mp.y) / (c1pm - c2pm);
this.arcCenter.y = (c2pm * (this.arcCenter.x - c2Mp.x)) + c2Mp.y;
},
_calcArcBounds: function() {
var validPoints = this.buildValidPointsForArc(),
minX = fabric.util.array.min(validPoints, 'x'),
minY = fabric.util.array.min(validPoints, 'y'),
maxX = fabric.util.array.max(validPoints, 'x'),
maxY = fabric.util.array.max(validPoints, 'y'),
width = (maxX - minX) || 1,
height = (maxY - minY) || 1;
this.arcBounds = {
x: minX,
y: minY,
width: width,
height: height
}
},
buildValidPointsForArc: function() {
var direction = this.getRenderingDirection(),
possibleBoundingPoints = this.points.concat();
!this.arcAngles && this._calcArcAngles();
if (direction) {
for (var i = 1; i <= 4; i++) {
var randomAngle = i * (PI / 2);
if (this.arcAngles.startAngle < this.arcAngles.endAngle) {
!(this.arcAngles.startAngle <= randomAngle && randomAngle <= this.arcAngles.endAngle) &&
possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
} else {
(this.arcAngles.endAngle <= randomAngle && randomAngle <= this.arcAngles.startAngle) &&
possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
}
}
} else {
for (var i = 4; i >= 1; i--) {
var randomAngle = i * (PI / 2);
if (this.arcAngles.startAngle < this.arcAngles.endAngle) {
(this.arcAngles.startAngle <= randomAngle && randomAngle <= this.arcAngles.endAngle) &&
possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
} else {
!(this.arcAngles.endAngle <= randomAngle && randomAngle <= this.arcAngles.startAngle) &&
possibleBoundingPoints.push(this.generateArcPoint(randomAngle));
}
}
}
return possibleBoundingPoints;
},
generateArcPoint: function(angle) {
return new fabric.Point(this.arcCenter.x + this.radius * Math.cos(angle), this.arcCenter.y + this.radius * Math.sin(angle));
},
_calcArcAngles: function() {
var angleKeyRepo = ["startAngle", "intermediateAngle", "endAngle"];
this.arcAngles = this.arcAngles || {};
this.points.forEach(function(point, index) {
var a = cMath.getAngle(this.arcCenter, point);
this.arcAngles[angleKeyRepo[index]] = a < 0 ? ((PI * 2) + a) :
a > 2 * PI ? ((PI * 2) - a) : a;
}, this);
},
getRenderingDirection: function() {
return (((this.points[1].x - this.points[0].x) * (this.points[2].y - this.points[0].y)) -
((this.points[1].y - this.points[0].y) * (this.points[2].x - this.points[0].x))) < 0;
},
_render: function(ctx, noTransform) {
if (!this.visible) {
return;
}
ctx.beginPath();
ctx.arc(
noTransform ? this.left : 0,
noTransform ? this.top : 0,
this.radius,
this.arcAngles.startAngle,
this.arcAngles.endAngle,
this.getRenderingDirection()
);
this._renderFill(ctx);
this._renderStroke(ctx);
},
toObject: function (propertiesToInclude) {
return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), {
points: this.points
});
}
});
fabric.ThreePointArc.fromObject = function(object) {
return new fabric.ThreePointArc(object.points, object);
};
fabric.ThreePointArc.async = false;
fabric.util.cMath = {
getSlope : function (startPoint, endPoint) {
if (!startPoint || !endPoint) {
console.error('startPoint and endPoint are required to evaluate slope');
return;
}
// hack to get around the indefinte slope problem
if (endPoint.x == startPoint.x) startPoint.x = startPoint.x + 0.01;
if (endPoint.y == startPoint.y) endPoint.y = endPoint.y + 0.01;
return (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);
},
getMidPoint: function (startPoint, endPoint) {
if (!startPoint || !endPoint) {
console.error('startPoint and endPoint are required to evaluate slope');
return;
}
return { x: (startPoint.x + endPoint.x) / 2, y: (startPoint.y + endPoint.y) / 2 };
},
getAngle: function (startPoint, endPoint, isDegree) {
if (!startPoint || !endPoint) {
console.error('startPoint and endPoint are required to evaluate slope');
return;
}
var radians = Math.atan2((endPoint.y - startPoint.y), (endPoint.x - startPoint.x)),
degrees = fabric.util.radiansToDegrees(radians);
return isDegree ? degrees < 0 ? 360 + degrees : degrees : radians;
},
getLength: function (startPoint, endPoint) {
if (!startPoint || !endPoint) {
console.error('startPoint and endPoint are required to evaluate slope');
return;
}
return Math.sqrt(Math.pow(endPoint.y - startPoint.y, 2) + Math.pow(endPoint.x - startPoint.x, 2));
}
}
var canvas = new fabric.Canvas('c');
var startPoint = new fabric.Point(47.25423728813553, 56.91525423728814),
intermediatePoint = new fabric.Point( 76.33898305084739,19.8983050847458 ),
endPoint = new fabric.Point( 105.42372881355931,86 );
var arc = new fabric.ThreePointArc([startPoint, intermediatePoint, endPoint] , {
fill: "#FF0000",
stroke: "#000",
strokeWidth: 10
});
canvas.add(arc);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.10/fabric.js"></script>
<canvas id="c" width="800" height="800"></canvas>
I'm trying to create a three point arc on fabric js (ver: 1.4.10). What I want is exactly like this:- https://www.screencast.com/t/dMLvcOduEF
I have a points array from which I'm calcualting the startAngle, endAngle, arcCenter, radius, bounding boxc height and widht, top and left. When I draw it, the actual object is not drawing where my mouse points. It adds up the left and top value with the arcCenter.x and arcCenter.y respectively and draws there. Is there any workaround so that my arc can be drawn where my pointer is ?. So that I don't need to write any extra lines of code for moving, rotating and scaling.
What I'm getting is like this:- https://www.screencast.com/t/V9MUgB3pB
added the fiddle!!
Is there any workaround for this ??

How do I reverse a scanline using the jpeg-js module/node JS buffer?

I've been fiddling around with the jpeg-js module and Node JS Buffer, and attempting to create a small command line program that modifies the decoded JPEG buffer data and creates a pattern of X number of reversed scanlines and X number of normal scanlines before saving a new JPEG. In other words, I'm looking to flip portions of the image, but not the entire image itself (plenty of modules that do such a thing, of course, but not the specific use case I have).
To create the reversed/normal line patterns, I've been reading/writing line by line, and saving a slice of that line to a variable, then starting at the end of scanline and incrementally going down by slices of 4 bytes (the alloc for an RGBA value) until I'm at the beginning of the line. Code for the program:
'use strict';
const fs = require('fs');
const jpeg = require('jpeg-js');
const getPixels = require('get-pixels');
let a = fs.readFileSync('./IMG_0006_2.jpg');
let d = Buffer.allocUnsafe(a.width * a.height * 4);
let c = jpeg.decode(a);
let val = false; // track whether normal or reversed scanlines
let lineWidth = b.width * 4;
let lineCount = 0;
let track = 0;
let track2 = 0;
let track3 = 0;
let curr, currLine; // storage for writing/reading scnalines, respectively
let limit = {
one: Math.floor(Math.random() * 141),
two: Math.floor(Math.random() * 151),
three: Math.floor(Math.random() * 121)
};
if (limit.one < 30) {
limit.one = 30;
}
if (limit.two < 40) {
limit.two = 40;
}
if (limit.two < 20) {
limit.two = 20;
}
let calc = {};
calc.floor = 0;
calc.ceil = 0 + lineWidth;
d.forEach(function(item, i) {
if (i % lineWidth === 0) {
lineCount++;
/* // alternate scanline type, currently disabled to figure out how to succesfully reverse image
if (lineCount > 1 && lineCount % limit.one === 0) {
// val = !val;
}
*/
if (lineCount === 1) {
val = !val; // setting alt scanline check to true initially
} else if (calc.floor + lineWidth < b.data.length - 1) {
calc.floor += lineWidth;
calc.ceil += lineWidth;
}
currLine = c.data.slice(calc.floor, calc.ceil); // current line
track = val ? lineWidth : 0; // tracking variable for reading from scanline
track2 = val ? 4 : 0; // tracking variable for writing from scanline
}
//check if reversed and writing variable has written 4 bytes for RGBA
//if so, set writing source to 4 bytes at end of line and read from there incrementally
if (val && track2 === 4) {
track2 = 0; // reset writing count
curr = currLine.slice(track - 4, track); // store 4 previous bytes as writing source
if (lineCount === 1 && lineWidth - track < 30) console.log(curr); //debug
} else {
curr = currLine; //set normal scanline
}
d[i] = curr[track2];
// check if there is no match between data source and decoded image
if (d[i] !== curr[track2]) {
if (track3 < 50) {
console.log(i);
}
track3++;
}
track2++; //update tracking variable
track = val ? track - 1 : track + 1; //update tracking variable
});
var rawImageData = {
data: d,
width: b.width,
height: b.height
};
console.log(b.data.length);
console.log('errors\t', track3);
var jpegImageData = jpeg.encode(rawImageData, 100);
fs.writeFile('foo2223.jpg', jpegImageData.data);
Alas, the reversed scanline code I've written does not properly. Unfortunately, I've only been able successfully reverse the red channel of my test image (see below left), with the blue and green channels just turning into vague blurs. The color scheme should look something like the right image.
What am I doing wrong here?
For reversed lines, you stored slices of 4 bytes(4 bytes = 1 pixel), then write the first value of the pixel(red) correctly.
But in the next iteration, you overwrite the slice curr with currLine, rest of channels gets wrong values.
if (val && track2 === 4) {
track2 = 0; // reset writing count
curr = currLine.slice(track - 4, track); // store 4 previous bytes as writing source
if (lineCount === 1 && lineWidth - track < 30) console.log(curr); //debug
} else {
curr = currLine; //set normal scanline
}
Iteration 0: val == true, track2 == 4, set curr to next pixel, write red channel.
Iteration 1: val == true, track2 == 1, (val && track2 === 4) == false, set curr to currLine, write green channel.
You can move track2 === 4 branch to avoid this:
if (val) {
if (track2 === 4) {
track2 = 0; // reset writing count
curr = currLine.slice(track - 4, track); // store 4 previous bytes as writing source
if (lineCount === 1 && lineWidth - track < 30) console.log(curr); //debug
}
} else {
curr = currLine; //set normal scanline
}
Fixed code should look like this:
function flipAlt(input, output) {
const fs = require('fs');
const jpeg = require('jpeg-js');
let a = fs.readFileSync(input);
let b = jpeg.decode(a);
let d = Buffer.allocUnsafe(b.width * b.height * 4);
let val = false; // track whether normal or reversed scanlines
let lineWidth = b.width * 4;
let lineCount = 0;
let track = 0;
let track2 = 0;
let track3 = 0;
let curr, currLine; // storage for writing/reading scnalines, respectively
let limit = {
one: Math.floor(Math.random() * 141),
two: Math.floor(Math.random() * 151),
three: Math.floor(Math.random() * 121)
};
if (limit.one < 30) {
limit.one = 30;
}
if (limit.two < 40) {
limit.two = 40;
}
if (limit.two < 20) {
limit.two = 20;
}
let calc = {};
calc.floor = 0;
calc.ceil = 0 + lineWidth;
d.forEach(function(item, i) {
if (i % lineWidth === 0) {
lineCount++;
if (lineCount > 1) {
val = !val;
}
if (lineCount === 1) {
val = !val; // setting alt scanline check to true initially
} else if (calc.floor + lineWidth < b.data.length - 1) {
calc.floor += lineWidth;
calc.ceil += lineWidth;
}
currLine = b.data.slice(calc.floor, calc.ceil); // current line
track = val ? lineWidth : 0; // tracking variable for reading from scanline
track2 = val ? 4 : 0; // tracking variable for writing from scanline
}
//check if reversed and writing variable has written 4 bytes for RGBA
//if so, set writing source to 4 bytes at end of line and read from there incrementally
if (val) {
if (track2 === 4) {
track2 = 0; // reset writing count
curr = currLine.slice(track - 4, track); // store 4 previous bytes as writing source
if (lineCount === 1 && lineWidth - track < 30) console.log(curr); //debug
}
} else {
curr = currLine; //set normal scanline
}
d[i] = curr[track2];
// check if there is no match between data source and decoded image
if (d[i] !== curr[track2]) {
if (track3 < 50) {
console.log(i);
}
track3++;
}
track2++; //update tracking variable
track = val ? track - 1 : track + 1; //update tracking variable
});
var rawImageData = {
data: d,
width: b.width,
height: b.height
};
console.log(b.data.length);
console.log('errors\t', track3);
var jpegImageData = jpeg.encode(rawImageData, 100);
fs.writeFile(output, jpegImageData.data);
}
flipAlt('input.jpg', 'output.jpg');
Instead of tracking array indices, you can use utility library like lodash, it should make things easier:
function flipAlt(input, output) {
const fs = require('fs');
const jpeg = require('jpeg-js');
const _ = require('lodash');
const image = jpeg.decode(fs.readFileSync(input));
const lines = _.chunk(image.data, image.width*4);
const flipped = _.flatten(lines.map((line, index) => {
if (index % 2 != 0) {
return line;
}
const pixels = _.chunk(line, 4);
return _.flatten(pixels.reverse());
}));
const imageData = jpeg.encode({
width: image.width,
height: image.height,
data: new Buffer(flipped)
}, 100).data;
fs.writeFile(output, imageData);
}
flipAlt('input.jpg', 'output.jpg');

Resources