Closed pliablepixels closed 7 years ago
Wouldn't a video be better suited? Didn't find any streaming method to use with gifshot - it was more or less "here, take this input and give me a blob when it's ready" What have you tried so far?
The problem is the media needs to be created in-phone (not server). So creating a video from a set of images is probably far more CPU intensive than a gif ? I have no problem if the output is a gif or a video -> as long as I can supply it sufficient images and it is able to stitch them into the animation without loading everything into memory (which is where I thought your library would be useful)
So far, I am only using gif shot as-is, and have had to limit the # of frames it can accept and have reduced the quality (sampling) - this results in me being able to create a GIF that is approx 70 frames worth of images @ 800x600 @ a lower sampling rate. If the supplied image array is > 70, I iterate through the array and remove every subsequent frame till array <=70. Beyond this magic number the app crashes because everything is loaded in memory. I can afford to remove images this way because the series of images is actually a camera feed that is recorded as images and stored in the server -> and deleting alternate frames is equivalent to reducing frames per second.
My gif shot code is here
If you are asking what have I tried so far in terms of integrating your library with gifshot, that is where the tiny bounty comes in - I don't know how to even start :p
I have just realized gifshot isn't what makes the gif. It's but a wrapper around the deanm/omggif source. omggif don't seem to be able to stream/flush the binaries at all... gifshot only helps you create gifs from different input...
It may help to switch the base64 to a blob... base64 takes up more memory...
How large is the final gif in size (mb) if it contains 70 frames
With sampling of 20, and 800x600 resize, it takes 22MB on disk While gif shot is constructing the image, it takes up 920MB of memory I've uploaded the image here if it helps at all. You won't see much movement except for the time moving as I forced an alarm as a test.
gifShot creates it in base64, so I do this https://github.com/pliablepixels/zmNinja/blob/master/www/js/EventCtrl.js#L989
I will be out of town till Thu, but will keep sneaking in - thanks for any insight!
Okey, the total size of it shouldn't come anywhere close to 920mb if you only use 70 images and produces one single gif that is 22mb in size afterwards... It must clearly be some memory leak or bad usage in the other libs.
I think you shouldn't have to use StreamSaver for such low result
Hi, I don't have the 70 images at the moment - I am out of town - but this is one of the images - just copying it 70 times should produce a similar result https://drive.google.com/file/d/0B3iQz0D8vxltc3pMOEt2a1ZMcTA/view?usp=sharing
Do note the 22MB is after I reduce size of the image to 800x600 and only take 70 frames. In reality, the # of frames are typically 3-4000. I can't use that many frames because I run out of memory.
I have been looking into using https://github.com/antimatter15/whammy as an alternative source and produce a video instead of a gif.
Tried to help them a bit to reduce memory https://github.com/antimatter15/whammy/issues/29 But it still had reference to all the frames durning compile 👎 It had no stdin or stdout stream-a-like mechanism (aka pipe) you want to send some pices and recive some when the RAM is taking a toll
Cuz both whammy and gifshot uses canvas, i remembered "hey it's actually possible to get a stream from canvas element!!!"
Then i just looked back to what i had done in the examples in the Readme
So i just used the that as a reference and created a simple MediaRecorder from frames uploaded by a file input (the result was a 233K webp video 1 sec long) (never figured out how to controll the frame rate or the video format - think it's possible to choose mp4 file format also...)
I have uploaded the final result and the frames used to create them to dropbox https://dl.dropboxusercontent.com/u/3464804/door.zip
Here is also the code i used to make it: https://jsfiddle.net/jrftnp13/1/
Note with this method, memory & size should not extend so large for you so you ever need StreamSaver the result was not even ½MB... StreamSaver is good for size over ~500MB But if you want to add all 4000 frames and the size goes way up then you can look at the readme example and hook that into ondataavalible
LoL, just realize when comparing the gif to my video that i created the video backwards ^^ Probably had to do with something of how i exported/named all the frames in photoshop :P
Hi Jimmy, am I just supposed to run the demo, upload a few pictures and it will show the link for the video when ready? I tried several times to upload a few images but it seems to keep waiting. Will this work on mobiles?
Finally, my app is on ionic/angular 1 - not v2 (so no ES6 features). I've set up a blank starter template http://codepen.io/pliablepixels/pen/qqrMQa -- I suppose your example can be ported to pre ES6 too?
ES6 has nothing to do with angular 2. You are mixing it with typescript
And yes you should just run the demo and upload a few pics. What browsers are you testing? What dose the console log say? Try latest chrome
Thanks - it worked in Chrome. Doesn't work on Safari - I'll run the debugger in more detail to see where it is getting stuck - my solution needs to work on both iOS (Safari) and Android (Chrome + WebView on Android 5.0+ phones) so I really am looking for a cross platform solution. Really appreciate your inputs so far.
well, the MediaRecorder is limited to chrome, opera and firefox - ref: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder#Browser_compatibility
Ah shucks - this was so close :-(
Maybe found something: https://github.com/devongovett/gif-stream That is if you still feel like you want a gif as output
Thank you - I read with interest your comments here - my knowledge of JS isn't very deep, but @bfred-it is absolutely right - the amount of memory consumed while creating the GIF is of magnitudes higher than the resulting GIF on disk.
Were you able to find any mechanism that worked? I also saw your issue on gif-stream - it seems to use a node specific approach - I am looking for something that will work on both Chrome and Safari and without node.
Think i have solved it... https://jsfiddle.net/jrftnp13/4/ I converted the omggif writer to a stream
The difference now is that whenever you have a other stream that consumes the gif stream it will pull for more data. And for each pull you got you should add a new frame either by controller.enqueue
or resolve a promise
So if this part of the code had not been there
let conf = {headers: {'content-type': 'image/gif'}}
new Response(gif, conf).blob().then(blob => {
console.log(blob.url())
})
then the gifWriter wouldn't write any data cuz you have nothing else that is consuming the stream so there is no point for gif writer to do any work
A lower level of getting a blob would be like this (this is what Response.prototype.blob dose in the background only diffrent now is that you don't need a fetch polyfill)
var chunks = []
reader = gif.getReader()
pull = () => {
// Each time you call `read()` here will make a pull (which result in you pull
// function beeing executed and you end up painting a frame to the canvas)
reader.read().then(({done, value}) => {
// Value is first going to be some gif headers
// other time it's going to be the biniaries of each frame
// and the last of them when done == true it will only be one byte
chunks.push(value)
return done ? chunks : pull()
})
}
pull().then(chunks => {
console.log(new Blob(chunks))
})
So it it's almost something like this
.pipe( [img1, img2, img3] )
.pipe(img_to_pixel)
.pipe(gifWriter)
.pipe(output)
But if you would for example stop somewhere in the middle of the output
the rest of the stream would halt and be put in a error'ed state meaning img3 will not even be read... the rest of the chain breaks if something, somewhere goes wrong
Hi Jimmy, any chance you can convert your code to pre-ES6 syntax? Maybe fork this codepen and put your code there? http://codepen.io/pliablepixels/pen/qqrMQa
You could use https://babeljs.io/repl/ to transpile it to es5
I am running the latest demo on Chrome Version 54.0.2840.98 (64-bit) for OS X - the download button never shows up - seems to be getting stuck at processing the images
Did you remove this part?
// force palette to be power of 2
let powof2 = 1
while (powof2 < palette.length) powof2 <<= 1
palette.length = powof2
Also this time i didn't print the download button on the page - i just logged the blob url
no - I saw that code and thought that was your override :-) the code worked on the door image I sent you, but not on another random jpg I pulled from my camera.
On another note, can you help porting this to pre-ES6 in a form I can import into my project using just a <script src>
like GIFShot? I tried using babel with your code but it gave a lot of errors.
I'll be happy to offer up my proposed bounty for all the work you have done so far anyway, I'd appreciate if it could be ported to an environment I am familiar with :)
<!-- Probably best if you download this resources -->
<!-- screw FileReader if you want that -->
<script src="https://cdn.rawgit.com/jimmywarting/Screw-FileReader/master/index.js"></script>
<!-- web streams polyfill (includes both Redable & Writable api) -->
<script src="https://cdn.rawgit.com/creatorrr/web-streams-polyfill/master/dist/polyfill.min.js"></script>
<script src="Gif Writer"></script>
<script src="code to adapt"></script>
And since all your targeted browser don't support fetch or especially the WebStream api then you are going to need
var chunks = [];
var reader = gif.getReader();
function pull() {
reader.read().then(function (result) {
chunks.push(result.value);
return result.done ? chunks : pull();
});
}
pull().then(function (chunks) {
console.log(new Blob(chunks, {type: "image/gif"}));
});
Thank you for your amazing help so far. I'll adapt it to my codepen tomorrow so I can run it by you - I'm sure I'll have a few more questions before I actually get it working in the codepen I posted above.
Till then, how do I go about giving you the bounty? Do you have a bounty source team or PayPal id?
Progress so far http://codepen.io/pliablepixels/pen/qqrMQa --> I'm trying to adapt your code so there are no images to upload - I am passing an array of URLs. Is there an easy way for me to emulate the upload function and return a correct file array just like input = file does?
Please check your email
@jimmywarting can you review your forced power of 2 code? gifwriter requires the palette colors to be powers of 2 and <=256. The current code often converts it to 4096, resulting in gif writer error. I'll try and limit it to 256 and see what happens
forcing it to a max of 256 produces really dithered colors - any thoughts ?
Wow... That's bad
Will see if I can think of something
so it looks like gif shot is using some image quantization to reduce the palette - GIF can't exceed 256
Looks like https://github.com/devongovett/neuquant is needed
right - the gif shot version looks like its packed into a module https://github.com/yahoo/gifshot/blob/master/src/modules/dependencies/NeuQuant.js
Okay never mind - it was easier to just put NeoQuant back This is my final code - I'll post memory results after more testing and close soon! Thanks for your amazing help
.then(function(img)
{
console.log ("URL="+frame);
URL.revokeObjectURL(img.src);
ctx.drawImage(img, 0, 0);
var data = ctx.getImageData(0, 0, w, h).data;
var rgbComponents = dataToRGB(data, w, h);
var nq = new NeuQuant(rgbComponents, rgbComponents.length, 10);
var paletteRGB = nq.process();
var paletteArray = new Uint32Array(componentizedPaletteToArray(paletteRGB));
var numberPixels = w * h;
var k = 0, i, r, g, b;
for (i = 0; i < numberPixels; i++) {
r = rgbComponents[k++];
g = rgbComponents[k++];
b = rgbComponents[k++];
pixels[i] = nq.map(r, g, b);
}
controller.enqueue([0, 0, w, h, pixels,
{
palette: paletteArray,
delay: 5
}]);
});
hoping for the best
Huge difference. Benchmarked it on both iOS and Desktop (need to check android) Bottom line, an image set of around 60 images @ 800x600 that was topping 1GB memory (thusly crashing in iOS) in gifshot remains < 200MB in your modified version. Unfortunately, it looks like I need to convert to base64 to save to the iOS phone gallery which takes up 3x the memory at the end - even so, its a huge improvement. gifShot also converts obj.image to base64, which is where it would crash. I'll close this after I check on Android!
I do think this approach is noticeably slower than gifshot but not 100% sure.
try include bluebird to see if there is any noticeable differens in speed
It's weird, but after successful invocation of this routine, core angular seems to throw this error (I intercept it and continue, but I'm curious why it happens with your steps)
TypeError: undefined is not an object (evaluating 'parsed.protocol') caused by undefined
=
basically it looks like an http call is made with no config object somewhere in the code we are using
stack:
ionic.bundle.js:25642 TypeError: Cannot read property 'protocol' of undefined
at urlIsSameOrigin (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:31164:17)
at sendReq (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:23638:25)
at serverRequest (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:23357:16)
at processQueue (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:27879:28)
at file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:27895:27
at Scope.$eval (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:29158:28)
at Scope.$digest (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:28969:31)
at Scope.$apply (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:29263:24)
at file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:31030:36
at completeOutstandingRequest (file:///Users/winshars/projects/phonegap/zmNinja-mac.app/Contents/Resources/app.asar/lib/ionic/js/ionic.bundle.js:18706:10) undefined
I'm afraid i can't help you with that part, hard to debug a bundle version
no worries - I've zeroed into the issue - its when you are doing this
if (!frame) controller.close();}
I have no idea what that is doing but I get the feeling its not 'cleaning up' -> I'll explore more
problem solved, it was missing a return
after controller close, resulting in a null http.
android - memory crash for the same image set. Not in your part, but the part where I need to convert your code to base64 to store into phone gallery. sigh. I'll investigate android in more detail tomorrow. I couldn't collect memory graphs as dev tools came down with it. So I don't know how much was consumed - I'll investigate tomorrow if Dev Tools allows for a way to dynamically look at memory - without having to record it in timeline (if it crashes, its of no help)
memory consumption in android (before it crashed at base64encode)
So effectively, your part of the code is great. Now I need to find out how to write this to the gallery without a base64encode or at least not in memory.
I'm going to close this at this stage. Thanks @jimmywarting - your help has been amazing.
It's maybe still possible to use StreamSaver with this (if you are running chrome on android or the device supports service workers + native ReadableStream) Since GifWriter use web streams now. And since you included the web streams polyfill you could just do this:
var readableStream = $scope.createGif(files, img.width, img.height);
var fileStream = streamSaver.createWriteStream('image.gif')
readableStream.pipeTo(fileStream)
Thanks - this did not work on iOS (no readable stream) - I finally figured out how to resolve this -- its not necessary to convert to base64 to write to the photo gallery, you can download to a file and save that file to gallery. The problem however is devices crash if you try to write a large file to FS (>5MB or so). The solution was to write it in chunks
Then you can write each chunk you get from reader.read()
👍
Hi there, I posted this question in the gifshot repo
The situation here is I am using gif shot to create an animated GIF from multiple images. gifshot creates the blobs in memory, so obviously, after a while, it crashes after eating up all memory.
Is it somehow possible to combine gifshot and this library to be able to create an animated gif from a series of images, that can be save to disk/image gallery for mobiles - memory not being a limiting factor?
Happy to put the same $100 bounty on it I offered in the gif shot repo if any talented dev. can take this up :-)(bounty awarded to jimmywarting)