Skip to content

Instantly share code, notes, and snippets.

@brandonetter
Created April 8, 2021 20:34
Show Gist options
  • Save brandonetter/8659a34d8b5eb63bbdbe3e134127cfe2 to your computer and use it in GitHub Desktop.
Save brandonetter/8659a34d8b5eb63bbdbe3e134127cfe2 to your computer and use it in GitHub Desktop.
Animation Flipbook
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
<!DOCTYPE html>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightslider/1.1.6/css/lightslider.css"
integrity="sha512-+1GzNJIJQ0SwHimHEEDQ0jbyQuglxEdmQmKsu8KI7QkMPAnyDrL9TAnVyLPEttcTxlnLVzaQgxv2FpLCLtli0A=="
crossorigin="anonymous" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightslider/1.1.6/js/lightslider.js"
integrity="sha512-sww7U197vVXpRSffZdqfpqDU2SNoFvINLX4mXt1D6ZecxkhwcHmLj3QcL2cJ/aCxrTkUcaAa6EGmPK3Nfitygw=="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"></script>
<html>
<head>
<script src='http://fabricjs.com/lib/fabric.js'></script>
<meta charset="UTF-8">
<title>flipbook - </title>
<style>
:root {
--color-black: #141419;
--color-red: #A84B38;
--color-orange: #EA8220;
--color-orange-2: #F1471D;
--color-yellow: #FBC707;
--color-white: #FFFFFF;
}
@keyframes pulse {
from {
transform: scale3d(1, 1, 1);
filter: drop-shadow(16px 16px 24px rgba(5, 5, 5, 0.9))
}
50% {
transform: scale3d(1.5, 1.5, 1.5);
filter: drop-shadow(24px 24px 48px rgba(5, 5, 5, 0.5))
}
100% {
transform: scale3d(1, 1, 1);
filter: drop-shadow(16px 16px 24px rgba(5, 5, 5, 0.9))
}
}
* {
margin: 0;
padding: 0;
}
body {
font-family: monospace;
background: var(--color-black);
color: var(--color-orange-2);
width: 100%;
height: 100%;
overflow: hidden;
cursor: default;
}
a {
color: var(--color-orange-2);
text-decoration: none;
}
*::selection {
background: var(--color-orange-2);
color: var(--color-yellow);
}
header,
main {
position: relative;
top: 1rem;
left: 1rem;
}
header {
text-transform: uppercase;
}
header h1 {
font-family: Helvetica, sans-serif;
font-size: 3rem;
}
main div {
position: relative;
left: 2px;
}
img.logo {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 50vmin;
pointer-events: none;
}
#cc {
border: 4px solid rgba(73, 65, 57, 0.315);
display: flex;
align-items: center
}
p {
color: black;
}
.frame {
background-color: gray;
width: 250px;
}
.frame2 {
background-color: beige;
}
#c {
border-right: 4px solid rgba(73, 65, 57, 0.315);
display: flex;
align-items: center;
background-color: beige;
}
img#logo-haxe {
opacity: 0;
animation-delay: 0.3s;
animation-duration: 1s;
animation-fill-mode: both;
animation-name: pulse;
filter: drop-shadow(16px 16px 24px rgba(5, 5, 5, 0.9));
z-index: 2;
}
footer {
position: fixed;
right: 0;
bottom: 0;
display: none;
}
p {
height: 120px;
}
.o {
height: 120px;
}
.x {
background-color: orange;
height: 120px;
}
</style>
</head>
<body>
<h4 id='Cpath'></h4>
<header>
<h1>Flipbook</h1>
</header>
<div id='cc'>
<canvas id="c"></canvas>
<div style='margin:10px'>
<button onclick='lockUnlock()'>Lock/Unlock</button><br>
<button onclick='Copy()'>Copy</button>
<button onclick='Paste()'>Paste</button><br>
<button onclick='groupSelection()'>Group Selection</button><br>
<button onclick='ungroupSelection()'>UnGroup Selection</button><br>
<input onchange='colorPickChange()' id='lineWidth' type="range" min="1" max="90" value="2"></input><br>
<input onchange='colorPickChange()' type='color' id='colorPick'></input>
<button onclick='toggleDraw()' id='toggleDrawButton'>Draw</button><br>
<button onclick='deleteSelection()'>Delete Selection</button><br>
<button onclick='runAnimate()'>Animate</button><br>
<button onclick='bringForward()'>Bring Forward</button>
</div>
</div>
<script>
window.globals = {
gallery: "",
c_top: 40,
c_left: 120,
c_width: 400,
c_height: 300,
path: '/flipbook/'
};
console.log(globals.c_left);
const {
ipcRenderer
} = require('electron');
ipcRenderer.on('send-path', function (event, path) {
globals.path = path;
console.log(globals.path);
document.getElementById('Cpath').innerHTML = globals.path;
});
// Some data that will be sent to the main process
let Data = {
message: "Hi",
someData: "Let's go"
};
function lockUnlock() {
if (!canvas.getActiveObject()) {
obj = canvas.getObjects();
obj.forEach(function (item, i) {
console.log('plz work');
//item.setColor('red');
if (item.get('name') != 'canvasBorder')
item.set('selectable', true);
canvas.renderAll();
});
}
canvas.getActiveObject().selectable = false;
canvas.getActiveObject().set('selectable', false);
canvas.getActiveObject().set('name', 'nonsel');
canvas.discardActiveObject();
canvas.requestRenderAll();
}
function bringForward() {
canvas.bringForward(canvas.getActiveObject());
canvas.requestRenderAll();
}
function ungroupSelection() {
if (!canvas.getActiveObject()) {
return;
}
if (canvas.getActiveObject().type !== 'group') {
return;
}
canvas.getActiveObject().toActiveSelection();
canvas.requestRenderAll();
}
function Copy() {
canvas.getActiveObject().clone(function (cloned) {
_clipboard = cloned;
});
}
function Paste() {
_clipboard.clone(function (clonedObj) {
canvas.discardActiveObject();
clonedObj.set({
left: clonedObj.left + 10,
top: clonedObj.top + 10,
evented: true,
});
if (clonedObj.type === 'activeSelection') {
clonedObj.canvas = canvas;
clonedObj.forEachObject(function (obj) {
canvas.add(obj);
});
clonedObj.setCoords();
} else {
canvas.add(clonedObj);
}
_clipboard.top += 10;
_clipboard.left += 10;
canvas.setActiveObject(clonedObj);
canvas.requestRenderAll();
});
}
function zeropad(value, padding) {
var zeroes = new Array(padding + 1).join("0");
return (zeroes + value).slice(-padding);
}
function groupSelection() {
if (!canvas.getActiveObject()) {
return;
}
if (canvas.getActiveObject().type !== 'activeSelection') {
return;
}
canvas.getActiveObject().toGroup();
canvas.requestRenderAll();
}
function colorPickChange() {
canvas.freeDrawingBrush.width = parseInt(document.getElementById('lineWidth').value, 10) || 1;
canvas.freeDrawingBrush.color = document.getElementById('colorPick').value;
}
function runAnimate() {
for (let i = 0; i < slideArray.length; i++) {
setTimeout(function () {
canvasLoad(slideArray[i].canvas, i, true);
}, (i + 1) * 100);
if (i == slideArray.length - 1) {
setTimeout(function () {
canvasLoad(slideArray[i].canvas, i, true);
}, (i + 1) * 270);
setTimeout(function () {
ipcRenderer.send('request-animate');
}, (i + 1) * 370);
}
}
}
function deleteSelection() {
canvas.remove(canvas.getActiveObject());
}
function toggleDraw() {
if (canvas.isDrawingMode) {
document.getElementById('toggleDrawButton').innerText = 'Draw';
} else {
document.getElementById('toggleDrawButton').innerText = 'Grab';
}
canvas.freeDrawingBrush.color = document.getElementById('colorPick').value;
canvas.isDrawingMode = !canvas.isDrawingMode;
}
let LightSlideSettings = {
item: 24,
currentPagerPosition: 'left'
}
var canvas = new fabric.Canvas("c");
canvas.setWidth(window.innerWidth * 0.8);
canvas.setHeight(window.innerHeight - 210);
canvas.calcOffset();
function reportWindowSize() {
canvas.setWidth(window.innerWidth * 0.8);
canvas.setHeight(window.innerHeight - 210);
canvas.calcOffset();
}
var green = new fabric.Rect({
top: globals.c_top - 2,
left: globals.c_left - 2,
width: globals.c_width + 4,
height: globals.c_height + 4,
fill: 'transparent',
strokeWidth: 2,
stroke: "#880E4F",
"selectable": false,
"evented": false,
name: 'canvasBorder'
});
canvas.add(green);
canvas.renderAll();
window.onresize = reportWindowSize;
function saveFrame(frame, send) {
var s1 = new Slide(JSON.stringify(canvas.toJSON(['selectable', 'evented', 'name'])), frame);
let Data1 = {
canvas: cropPlusExport(globals.c_width),
name: frame
};
if (send) {
ipcRenderer.send('request-mainprocess-action', Data1);
}
slideArray[frame] = s1;
globals.gallery.destroy();
document.getElementById("lightSlider").innerHTML = '';
for (i = 0; i < slideArray.length; i++) {
var s = slideArray[i];
var leadingz = zeropad(i, 3);
var fill = "o";
if (i == activeFrame) {
fill = "x";
}
document.getElementById("lightSlider").innerHTML += "<li class='frame' onclick='canvasLoad(`" + s.canvas +
"`," + i + ",false)'><p class='" + fill + "'>" + leadingz + "<br>" + fill + "</p></li>";
}
var sn = $("#lightSlider").lightSlider(LightSlideSettings);
globals.gallery = sn;
globals.gallery.refresh();
}
class Slide {
constructor(fabricCanvas, name, thumb) {
this.canvas = fabricCanvas;
this.name = name;
this.thumbnail = thumb;
}
}
var slideArray = [];
var s1 = new Slide(JSON.stringify(canvas.toJSON(['selectable', 'evented', 'name'])), "000");
let Data1 = {
canvas: cropPlusExport(globals.c_width),
name: "000"
};
ipcRenderer.send('request-mainprocess-action', Data1);
slideArray[0] = s1;
activeFrame = 0;
function canvasLoad(json, frame, send) {
saveFrame(activeFrame, send);
activeFrame = frame;
canvas.loadFromJSON(json);
obj = canvas.getObjects();
obj.forEach(function (item, i) {
if (item.get('name') == 'canvasBorder') {
item.bringToFront();
}
});
saveFrame(activeFrame, false);
canvas.renderAll();
}
function addSlide() {
var ct = slideArray.length;
var s1 = new Slide(JSON.stringify(canvas.toJSON(['selectable', 'evented', 'name'])), ct);
let Data1 = {
canvas: cropPlusExport(globals.c_width),
name: ct
};
ipcRenderer.send('request-mainprocess-action', Data1);
slideArray[ct] = s1;
var leadingz = zeropad(ct - 1, 3);
var slider = "<li class='frame' onclick='canvasLoad(`" + s1.canvas + "`," + ct - 1 + ",false)'><p>" + leadingz +
"</p></li>";
globals.gallery.destroy();
document.getElementById("lightSlider").innerHTML = '';
for (i = 0; i < slideArray.length; i++) {
var s = slideArray[i];
var fill = "o";
if (i == activeFrame) {
fill = "x";
}
var leadingz = zeropad(i, 3);
document.getElementById("lightSlider").innerHTML += "<li class='frame' onclick='canvasLoad(`" + s.canvas +
"`," + i + ")'><p class='" + fill + "'>" + leadingz + "<br>" + fill + "</p></li>";
}
var sn = $("#lightSlider").lightSlider(LightSlideSettings);
globals.gallery = sn;
globals.gallery.refresh();
}
function cropPlusExport(global) {
var tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
console.log(globals);
tempCanvas.width = globals.c_width;
tempCanvas.height = globals.c_height;
var canvas_image = new Image();
canvas_image.src = canvas.toDataURL();
tCtx.drawImage(canvas_image, globals.c_left, globals.c_top, globals.c_width, globals.c_height, 0, 0, globals
.c_width, globals.c_height);
return (tempCanvas.toDataURL());
}
</script>
<button onclick='addSlide()' value="asd">CreateFrame</button>
<ul id="lightSlider" style='width:500px'>
</ul>
<script type="text/javascript">
$(document).ready(function () {
var slider = $("#lightSlider").lightSlider(LightSlideSettings);
globals.gallery = slider;
ipcRenderer.send('request-path');
});
</script>
</body>
</html>
// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron')
const { exec } = require("child_process");
const path = app.getPath('home');
var pathUse = path;
var folder = '/project-'+Math.floor(Math.random()*99999)+'/';
console.log(folder);
//const mainWindow;
//const mainWindow = null;
function createWindow () {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
var fs = require('fs');
fs.writeFile(pathUse+'/flipbook'+folder+'LOG.txt', "run", function (err) {
fs.mkdirSync(pathUse+'/flipbook'+folder, { recursive: true });
// path exists unless there was an error
});
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
const {ipcMain} = require('electron');
//const { app } = require('electron');
// Attach listener in the main process with the given ID
ipcMain.on('request-animate', (event, arg) => {
console.log('asd');
exec("ffmpeg -framerate 5 -y -i "+pathUse+'/flipbook'+folder+"%03d.png -plays 0 "+pathUse+'/flipbook'+folder+"output.mp4", (error, stdout, stderr) => {
if (error) {
console.log(`error: ${error.message}`);
return;
}
if (stderr) {
console.log(`stderr: ${stderr}`);
return;
}
console.log(`stdout: ${stdout}`);
});
});
ipcMain.on('request-path',(event,arg)=>{
var windows=BrowserWindow.getAllWindows();
windows[0].webContents.send('send-path', pathUse+'/flipbook'+folder);
});
ipcMain.on('request-set-Dev',(event,arg)=>{
pathUse = arg.path;
if(arg.mode == false){
pathUse = path;
}
console.log("dmode:"+arg.mode);
console.log("path:"+arg.path);
//var windows=BrowserWindow.getAllWindows();
//windows[0].webContents.send('send-path', path);
});
ipcMain.on('request-mainprocess-action', (event, arg) => {
// Displays the object sent from the renderer process:
//{
// message: "Hi",
// someData: "Let's go"
//}
Rexec(arg);
});
function zeropad(value, padding) {
var zeroes = new Array(padding+1).join("0");
return (zeroes + value).slice(-padding);
}
Rexec = function(canvas) {
var fs = require('fs');
//const dialog = require('electron').remote.dialog;
//var canvasBuffer = require('electron-canvas-to-buffer');
//dialog.showSaveDialog({title:'Testing a save dialog',defaultPath:'image.jpg'},saveCallback);
//canvas = mainWindow.document.getElementById('c');
// as a buffer
//var buffer = canvasBuffer(canvas, 'image/png')
// write canvas to file
fs = require('fs');
var data = canvas.canvas.replace(/^data:image\/\w+;base64,/, "");
var buf = new Buffer(data, 'base64');
//fs.writeFile('image.png', buf);
//var path = '/home/bran/Desktop/fiddle';
//var path = app.getAppPath();
fs.writeFile(pathUse+'/flipbook'+folder+zeropad(canvas.name,3)+'.png', buf, function (err) {
if (err) return console.log(err);
console.log(pathUse+'/flipbook'+folder+zeropad(canvas.name,3)+'.png');
});
}
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment