Hi I would like to load image on server, then change some pixels values and save new image as png. I came to his stage:
const pixels = require('image-pixels');
const {data, width, height} = await pixels('http://localhost:8081/image.png');
console.log(data);
// Todo change color of pixels
data[0] = 255;
data[1] = 255;
data[2] = 255;
// Todo: save new image
I found I can ´t use Image class in Node so I would like to ask if there is some workaround
output of console.log(data);
Uint8Array(400000000) [
255, 255, 255, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255, 210, 215, 211, 255, 210, 215, 211, 255,
210, 215, 211, 255,
... 399999900 more items
]
This helps me a lot: https://www.npmjs.com/package/image-output
var output = require('image-output')
// create chess pattern png from raw pixels data
output({
data: data,
width: width,
height: height
}, 'image.png')
I am working on a canvas video player with some special features based on frames of the video. To overcome the unreliable timing in the video HTML5 tag the videos we are using have a barcode embedded in each frame indicating the current frame number. Using the canvas getImageData method I can grab the pixels and read the barcode to get the frame number. This works great and I have a JSFiddle demonstrating that it works (I couldn't get around CORS in this fiddle to serve the video to the canvas so to see it working you'll have to download the example video locally then upload it via the button. Not ideal but it works).
On certain mobile devices (only Android thus far) this logic breaks. The getImageData returns incorrect values.
It works correctly on my Samsung Galaxy S5 v6.0.1 but fails on a Google Pixel running android v7.1.2. I'll try to collect more data on which devices/OS versions it fails on.
For example, when playing on desktop the first iteration of getImageData returns:
Uint8ClampedArray(64) [3, 2, 3, 255, 255, 255, 255, 255, 246, 245, 247, 255, 243, 242, 243, 255, 241, 239, 241, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255, 242, 240, 242, 255]
which correctly gets computed as framenumber 1.
However on the galaxy, the first iteration returns:
Uint8ClampedArray(64) [255, 242, 217, 255, 255, 234, 209, 255, 41, 1, 1, 255, 254, 235, 210, 255, 255, 234, 209, 255, 50, 4, 0, 255, 254, 240, 215, 255, 255, 248, 224, 255, 255, 249, 225, 255, 255, 251, 232, 255, 255, 252, 233, 255, 255, 252, 233, 255, 255, 253, 234, 255, 255, 255, 237, 255, 255, 255, 237, 255, 28, 1, 1, 255]
I read that certain devices may being doing additional smoothing so I've been playing around with disabling it in the context via:
this.ctx.mozImageSmoothingEnabled = false;
this.ctx.webkitImageSmoothingEnabled = false;
this.ctx.msImageSmoothingEnabled = false;
this.ctx.imageSmoothingEnabled = false;
But it didn't help.
Here is the code being used in the JSFiddle.
var frameNumberDiv = document.getElementById('frameNumber');
function load() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var video = document.getElementById('video');
canvas.width = 568;
canvas.height = 640;
video.addEventListener('play', function() {
var that = this; //cache
(function loop() {
if (!that.paused && !that.ended) {
ctx.drawImage(that, 0, 0);
var pixels = ctx.getImageData(0, 320 - 1, 16, 1).data;
getFrameNumber(pixels);
setTimeout(loop, 1000 / 30); // drawing at 30fps
}
})();
}, 0);
}
function getFrameNumber(pixels) {
let j = 0;
let thisFrameNumber = 0;
let str = "Pixels: ";
for (let i = 0; i < 16; i++) {
str += pixels[j] + " ";
thisFrameNumber += getBinary(pixels[j], i);
j += 4;
}
document.getElementById('frameNumber').innerHTML = "FrameNumber: " + thisFrameNumber;
}
function getBinary(pixel, binaryPlace) {
const binary = [1, 2, 4, 8, 16, 32, 64, 128, 256,
512, 1024, 2048, 4096, 8192, 16384, 32768
];
if (pixel > 128) return 0;
if (pixel < 128 && binary[binaryPlace]) {
return binary[binaryPlace]
} else {
return 0;
}
}
(function localFileVideoPlayer() {
'use strict';
var URL = window.URL || window.webkitURL;
var displayMessage = function(message, isError) {
var element = document.querySelector('#message');
element.innerHTML = message;
element.className = isError ? 'error' : 'info';
}
var playSelectedFile = function(event) {
console.log("Playing");
var file = this.files[0];
var type = file.type;
var videoNode = document.querySelector('video');
var canPlay = videoNode.canPlayType(type);
if (canPlay === '') canPlay = 'no';
var message = 'Can play type "' + type + '": ' + canPlay;
var isError = canPlay === 'no';
displayMessage(message, isError);
if (isError) {
return;
}
var fileURL = URL.createObjectURL(file);
videoNode.src = fileURL;
load();
}
var inputNode = document.querySelector('input')
inputNode.addEventListener('change', playSelectedFile, false)
})();
EDIT
Works on a Nexus 6P running Android v6.0
Works on a Samsung 6 (Samsung SM G920A) running Android v 5.0.2
It DOES NOT work on a Samsung Galaxy S7 (SAMSUNG-SM-G935A) running Android v7.0
Could this possibly be an Android 7 issue?
Edit 2
In response to a question in the comments:
videoNode.videoHeight and videoWidth are both 0 on the google pixel for their entire existence but this is the same as on desktop. In both of the devices that don't work that I've encountered the image of each frame is fully painted. I'll attach a screen shot from the google pixel. When paused it consistently reads the same number. In other words it is not jumping around so whatever it is reading is truly on the frame of the video.
EDIT 3: Discovery
I believe that I've made a relevant discovery/realization which I should have seen earlier.
When looking at the output of getImageData on the broken device I was stepping through line by line. What I hadn't (and should have) noticed was that the video element was continuing slightly after it hit my break points / debugger statements. By the time the getImageData method was executed the video had moved past the next frame. So, the scanned barcode was actually for a much later frame than expected.
I added some console log statements and let it run naturally. Looking at the output I can see a much more recognizable pattern.
Here is the first few readings on the google pixel:
Uint8ClampedArray(64) [255, 255, 255, 255, 246, 246, 246, 255, 243, 243, 243, 255, 240, 240, 241, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 241, 241, 242, 255, 2, 2, 2, 255]
Uint8ClampedArray(64) [5, 5, 5, 255, 255, 255, 255, 255, 251, 251, 251, 255, 247, 247, 248, 255, 245, 245, 245, 255, 245, 245, 245, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 246, 246, 246, 255, 6, 3, 2, 255]
Uint8ClampedArray(64) [235, 231, 230, 255, 17, 12, 12, 255, 252, 247, 247, 255, 255, 255, 255, 255, 255, 254, 254, 255, 255, 253, 253, 255, 255, 252, 251, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 255, 253, 253, 255, 7, 3, 1, 255]
Uint8ClampedArray(64) [26, 15, 14, 255, 4, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10, 0, 0, 255]
As you may notice, the results seem to be correct however they are shifted one pixel to the left.
I modified the JSFiddle slightly to shift the getImageData read over by a pixel and it gives the exact same response as on the Pixel.
var pixels = ctx.getImageData(1, 320 - 1, 16, 1).data;
Doing -1 seems to have no effect.
So, for some reason these devices are either shifting the entire texture over by a pixel or there is something wrong with the getImageData Method.
EDIT 4
As an experiment I reconfigured my code to use a webGL texture. Same behaviour on desktop/mobile devices. This allowed me to use -1 as the x target using gl.readPixels. I was hoping that by skipping using canvas the entire image would be stored in memory and I could access the pixel data I needed.... Didn't work but here is the data it produced which shows that it is also shifted using purely webGL.
Uint8Array(64) [0, 0, 0, 0, 255, 248, 248, 255, 25, 18, 18, 255, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]
Uint8Array(64) [0, 0, 0, 0, 255, 254, 247, 255, 255, 244, 236, 255, 48, 18, 10, 255, 254, 246, 238, 255, 255, 247, 239, 255, 255, 247, 239, 255, 255, 248, 241, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 251, 243, 255, 255, 250, 240, 255, 255, 250, 240, 255]
Uint8Array(64) [0, 0, 0, 0, 31, 0, 0, 255, 254, 243, 230, 255, 43, 6, 1, 255, 254, 243, 230, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 231, 255, 255, 244, 229, 255, 255, 244, 229, 255]
Using:
gl.readPixels(-1, height, 16, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
Edit 5
Ok, I promised I would get more details on which devices were failing/not. I had some online QA testing done using a slightly modified JSFiddle. I modified it slightly to help make it a bit more idiot proof for the general public to work with.
The responses were unfortunately fairly mixed. I was hoping it would be isolated to Android 7 but that doesn't seem to be the case.
I have a CSV on my google drive with the results of this test. Not that these tests are 100% reliable but it seems like it's just some random devices......
I have been going through the same problem and just couldn't get it. I finally have the answer in my case which sounds 99% like your problem as well.
It's the pixel density!!
The pixel densities are different on all the devices you mentioned and the ones that have probably 1 dpr (device pixel ratio) are working correctly, whereas the others are not.
So in my case using p5js I set the pixel density to be 1 and it worked like a charm;
pixelDensity(1);
So set it to be 1 dpr and you are most probably good to go!
I hope this helps some people out there because I spent quite a while on this problem.
Your code is fine so the problem here resides on how some androids render your barcode. The implementation of your barcode is way too small (16x1 pixels) and it's left indented.
Being left indented any anti alias that the device makes on the outer pixels will mess your barcode and give you incorrect results so, when working with video render, you definitely don't want to work on a one pixel safe area.
My suggestion would be to redo the barcodes to a bigger size - let's say 18 pixels height - only use black and white (no grays), center it on the video and the rest of the line paint it green: (in this example it's a barcode for "1")
Then make a GetImageData of the full 320x16 and get rid of everything that has a RGB of 0x255x0 (and approximate) and you have your barcode that would be able to use to get your FrameNumber.
I know you probably would like an answer where it wouldn't be necessary to redo the video but, in this case, the source is the problem.
I'm trying to get a div to transition from one background gradient to another. I have a magnetic scroll effect setup so that when the user scrolls down, or hits the arrow key down, the h1 title changes and the background gradient slowly transitions to another gradient. I have the magnetic scroll and h1 effects working fine. I even have the background gradients changing when they're supposed to. The problem is that I need these transitions to be super smooth. Currently they flash to the next gradient. I've been trying all kinds of hacks for a couple weeks now and can't seem to get anything to work. Visit the project live at alopexid.com.
My jQuery:
jQuery(document).ready(function($) {
$(document).foundation
//find page height
var windowHeight = $('html').height();
//find each line's section height
var pageHeight = windowHeight*7;
//script for scrolling text on home page.
$(window).scroll(function(event){
var currentHeight = $('body').scrollTop();
//load in static home page if bottom of scroll is reached
//alert(currentHeight);
//grad1
if (currentHeight == 0 && currentHeight < windowHeight){
//$(".home-gradient").css("background", "linear-gradient(135deg, #65c9de 0%, #cbd45a 100%)");
$("#home-gradient").removeAttr('style');
$(".home-gradient").removeClass('grad2');
$(".home-gradient").addClass('grad1');
}
//grad2
else if (currentHeight >= windowHeight && currentHeight < windowHeight*2){
//$(".home-gradient").css("background", "linear-gradient(135deg, #cbd45a 0%, #009688 100%)");
$("#home-gradient").removeAttr('style');
$(".home-gradient").removeClass('grad1');
$(".home-gradient").removeClass('grad3');
$(".home-gradient").addClass('grad2');
}
//grad3
else if (currentHeight >= windowHeight*2 && currentHeight < windowHeight*3){
//$(".home-gradient").css("background", "linear-gradient(135deg, #009688 0%, #FFC107 100%)");
$(".home-gradient").removeClass('grad2');
$(".home-gradient").removeClass('grad4');
$(".home-gradient").addClass('grad3');
}
//grad4
else if (currentHeight >= windowHeight*3 && currentHeight < windowHeight*4){
//$(".home-gradient").css("background", "linear-gradient(135deg, #FFC107 0%, #E91E63 100%)");
$(".home-gradient").removeClass('grad3');
$(".home-gradient").removeClass('grad5');
$(".home-gradient").addClass('grad4');
}
//grad5
else if (currentHeight >= windowHeight*4 && currentHeight < windowHeight*5){
//$(".home-gradient").css("background", "linear-gradient(135deg, #E91E63 0%, #9C27B0 100%)");
$(".home-gradient").removeClass('grad4');
$(".home-gradient").removeClass('grad6');
$(".home-gradient").addClass('grad5');
}
//grad6
else if (currentHeight >= windowHeight*5 && currentHeight < windowHeight*6){
//$(".home-gradient").css("background", "linear-gradient(135deg, #9C27B0 0%, #65c9de 100%)");
$(".home-gradient").removeClass('grad5');
$(".home-gradient").addClass('grad6');
}
//grad1
else if (currentHeight >= windowHeight*6) {
$(".home-gradient").removeClass('grad6');
$(".home-gradient").addClass('grad1');
$("#scroll-text").text("We are Alopex.");
$(".home-big-gradient").css("display", "none");
$(".home-gradient").css("display", "block");
$("#frame-logo").hide();
$("#scroll-guide").hide();
$(".header").show(1500);
$("#skip-intro").hide();
$('#weather-color').css('display', 'block');
$('#scroll-text').css('display', 'block');
window.magneticScroll = undefined;
return;
}
});
My HTML:
<div class="home-gradient" id="home-gradient">
</div>
<div class="home-big-gradient">
<h1 class="magnetic">We are Alopex.</h1>
<h1 class="magnetic">A digital marketing firm.</h1>
<h1 class="magnetic">Building websites & apps.</h1>
<h1 class="magnetic">In Palmer, Alaska.</h1>
<h1 class="magnetic">Some of us are designers.</h1>
<h1 class="magnetic">Some of us are developers.</h1>
<h1 class="magnetic">We are Alopex.</h1>
</div>
My CSS:
.home-gradient {
background-size: cover;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
opacity: .75;
position: fixed;
}
.grad1 {
background: rgb(101, 201, 222);
background: -moz-linear-gradient(-45deg, rgba(101, 201, 222, 1) 0%, rgba(203, 212, 90, 1) 100%); /* FF3.6-15 *///rgba(101, 201, 222, 1) #65C9DE | rgba(203, 212, 90, 1) #CBD45A
background: -webkit-linear-gradient(-45deg, rgba(101, 201, 222, 1) 0%, rgba(203, 212, 90, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(101, 201, 222, 1) 0%, rgba(203, 212, 90, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
background-image: -webkit-linear-gradient(-45deg, #65c9de 0%, #cbd45a 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#65c9de', endColorstr='#cbd45a',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.grad2 {
background: rgb(203, 212, 90);
background: -moz-linear-gradient(-45deg, rgba(203, 212, 90, 1) 0%, rgba(0, 150, 136, 1) 100%); /* FF3.6-15 *///rgba(203, 212, 90, 1) #CBD45A | rgba(0, 150, 136, 1) #009688
background: -webkit-linear-gradient(-45deg, rgba(203, 212, 90, 1) 0%, rgba(0, 150, 136, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(203, 212, 90, 1) 0%, rgba(0, 150, 136, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cbd45a', endColorstr='#009688',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.grad3 {
background: rgb(0, 150, 136);
background: -moz-linear-gradient(-45deg, rgba(0, 150, 136, 1) 0%, rgba(255, 193, 7, 1) 100%); /* FF3.6-15 */// rgba(0, 150, 136, 1) #009688 | rgba(255, 193, 7, 1) #FFC107
background: -webkit-linear-gradient(-45deg, rgba(0, 150, 136, 1) 0%, rgba(255, 193, 7, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(0, 150, 136, 1) 0%, rgba(255, 193, 7, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#009688', endColorstr='#FFC107',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.grad4 {
background: rgb(255, 193, 7);
background: -moz-linear-gradient(-45deg, rgba(255, 193, 7, 1) 0%, rgba(233, 30, 99, 1) 100%); /* FF3.6-15 */// rgba(255, 193, 7, 1) #FFC107 | rgba(233, 30, 99, 1) #E91E63
background: -webkit-linear-gradient(-45deg, rgba(255, 193, 7, 1) 0%, rgba(233, 30, 99, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(255, 193, 7, 1) 0%, rgba(233, 30, 99, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFC107', endColorstr='#E91E63',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.grad5 {
background: rgb(233, 30, 99);
background: -moz-linear-gradient(-45deg, rgba(233, 30, 99, 1) 0%, rgba(156, 39, 176, 1) 100%); /* FF3.6-15 */// rgba(233, 30, 99, 1) #E91E63 | rgba(156, 39, 176, 1) #9C27B0
background: -webkit-linear-gradient(-45deg, rgba(233, 30, 99, 1) 0%, rgba(156, 39, 176, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(233, 30, 99, 1) 0%, rgba(156, 39, 176, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#E91E63', endColorstr='#9C27B0',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.grad6 {
background: rgb(156, 39, 176);
background: -moz-linear-gradient(-45deg, rgba(156, 39, 176, 1) 0%, rgba(101, 201, 222, 1) 100%); /* FF3.6-15 */// rgba(156, 39, 176, 1) #9C27B0 | rgba(101, 201, 222, 1) #65C9DE
background: -webkit-linear-gradient(-45deg, rgba(156, 39, 176, 1) 0%, rgba(101, 201, 222, 1) 100%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(135deg, rgba(156, 39, 176, 1) 0%, rgba(101, 201, 222, 1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9C27B0', endColorstr='#65c9de',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
.home-big-gradient {
/*this is changed to block in desktop below around line 337*/
display: none;
}
Any help here would be amazing. I've read lots of comments about this not being possible but those are mostly from 2 to 5 years ago. There's got to be some kind of plugin or workaround out there. Thanks in advance for any help!
background gradient doesn't support transition but you could hack this creating another div that has a z-index bigger than your body or what contain your gradient but a lower z-index then the page content and opacity:0 so you can apply the gradient to this div then make it visible and finally apply the gradient to the body, delete it from the div and hide it.
I know that from this aswere you don't get how to do this so this DEMO will help you.
However I don't advice to use this strategy to avoid background gradient transition and keep the site as it is now.
Have you tried using ScrollReveal.js? You should be able to customize it to be able to transition the different backgrounds. And it would allow you to control the transitions by setting the delay property. I've used this library in some of my web pages and it's very easy to use, and you can pass it almost any element.