Last active
June 5, 2020 05:40
-
-
Save jspillers/3d9dac3c222ba85cbf475fd49c451b73 to your computer and use it in GitHub Desktop.
data-grid-gif
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> | |
<link rel='stylesheet' href="https://unpkg.com/@finos/[email protected]/dist/umd/material-dense.dark.css"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> | |
<script src="https://unpkg.com/@finos/perspective-workspace"></script> | |
<script src="https://unpkg.com/@finos/perspective-viewer-datagrid"></script> | |
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script> | |
<script src="https://unpkg.com/@finos/perspective"></script> | |
<script type="module"> | |
import { loadGifFrames, playGif } from './load-gif.js'; | |
//let gifUrl = 'https://i.giphy.com/oPu2IgQHwb3Qk.gif'; | |
//let gifUrl = 'https://i.giphy.com/l4KibK3JwaVo0CjDO.gif'; | |
let gifUrl = 'https://i.giphy.com/fZk0FD0wxQpb2.gif'; | |
let imageSizePercent = 20; | |
// haha, global vars... fight me. | |
window.isPlaying = true; | |
window.currentFrame = 0; | |
window.targetPlaybackSpeed = 15; | |
window.addEventListener('DOMContentLoaded', async () => { | |
const viewer = document.getElementsByTagName('perspective-viewer')[0]; | |
let frames; | |
const resetTable = async () => { | |
if (window.table) { | |
window.isPlaying = false; | |
await window.table.clear(); | |
await viewer.view.delete(); | |
await window.table.delete(); | |
window.table = undefined; | |
} | |
}; | |
const imageUrlLabel = document.querySelector('#ImageUrlContainer label span'); | |
const imageUrlInput = document.querySelector('#ImageUrl'); | |
imageUrlInput.value = gifUrl; | |
imageUrlInput.addEventListener('change', async (event) => { | |
gifUrl = event.target.value; | |
}); | |
const clamp = (val, min, max) => Math.min(Math.max(val, min), max); | |
const imageSizeSliderLabel = document.querySelector('#ImageSizeSliderContainer label span'); | |
const imageSizeSlider = document.querySelector('#ImageSizeSlider'); | |
imageSizeSlider.value = clamp(imageSizePercent, 1, 100); | |
imageSizeSliderLabel.innerHTML = imageSizeSlider.value + '%'; | |
imageSizeSlider.addEventListener('change', (event) => { | |
imageSizePercent = event.target.value; | |
imageSizeSliderLabel.innerHTML = imageSizePercent + '%'; | |
}); | |
const speedSliderLabel = document.querySelector('#PlaybackSpeedSliderContainer label span'); | |
const playbackSpeedSlider = document.querySelector('#PlaybackSpeedSlider'); | |
playbackSpeedSlider.value = clamp(window.targetPlaybackSpeed, 1, 24); | |
speedSliderLabel.innerHTML = playbackSpeedSlider.value + ' FPS'; | |
playbackSpeedSlider.addEventListener('change', (event) => { | |
window.targetPlaybackSpeed = event.target.value; | |
speedSliderLabel.innerHTML = window.targetPlaybackSpeed + ' FPS'; | |
}); | |
const loadButton = document.querySelector('button#Load'); | |
loadButton.addEventListener('click', async (event) => { | |
event.preventDefault(); | |
await resetTable(); | |
frames = await loadGifFrames(gifUrl); | |
console.log('load') | |
window.isPlaying = true; | |
playGif(frames, viewer, gifUrl, imageSizePercent); | |
}); | |
const playPauseButton = document.querySelector('button#PlayPause'); | |
const playPauseTextSpan = playPauseButton.querySelector('span'); | |
const playPauseIcon = playPauseButton.querySelector('i'); | |
playPauseButton.addEventListener('click', (event) => { | |
event.preventDefault(); | |
if (window.isPlaying) { | |
console.log('pause') | |
// is now paused | |
playPauseTextSpan.innerHTML = 'Play '; | |
playPauseIcon.classList.remove('fa-pause'); | |
playPauseIcon.classList.add('fa-play'); | |
window.isPlaying = false; | |
} else { | |
// is now playing | |
console.log('play') | |
playPauseTextSpan.innerHTML = 'Pause '; | |
playPauseIcon.classList.remove('fa-play'); | |
playPauseIcon.classList.add('fa-pause'); | |
window.isPlaying = true; | |
playGif(frames, viewer, gifUrl, imageSizePercent); | |
} | |
}); | |
viewer.addEventListener("perspective-datagrid-after-update", event => { | |
const datagrid = event.detail; | |
for (const td of datagrid.get_tds()) { | |
const metadata = datagrid.get_meta(td); | |
td.style.backgroundColor = metadata.value; | |
} | |
}); | |
frames = await loadGifFrames(gifUrl); | |
playGif(frames, viewer, gifUrl, imageSizePercent); | |
}); | |
</script> | |
</head> | |
<body> | |
<div class="main container"> | |
<form class="container" id="ImageControls"> | |
<div id="ImageUrlContainer"> | |
<label for="ImageUrl">Gif Url</label> | |
<input type="text" value="" id="ImageUrl" /> | |
<button id="Load">Load</button> | |
</div> | |
<div id="ImageSizeSliderContainer"> | |
<label for="ImageSizeSlider">Image size, percentage of original: <span>25%</span></label> | |
<input type="range" min="1" max="100" class="slider" id="ImageSizeSlider" /> | |
</div> | |
<div id="PlaybackSpeedSliderContainer"> | |
<label for="PlaybackSpeedSlider"> Target playback speed: <span>15 FPS</span></label> | |
<input type="range" min="1" max="24" class="slider" id="PlaybackSpeedSlider" /> | |
</div> | |
<button id="PlayPause" style=""><span>Pause </span><i class="fa fa-pause"></i></button> | |
</form> | |
<div class="container" id="PerspectiveViewer"> | |
</div> | |
</div> | |
<perspective-viewer plugin="datagrid" /> | |
<style> | |
.hidden { | |
display: none; | |
} | |
#ImageControls { | |
padding: 5px 15px; | |
color: #efefef; | |
} | |
#PlayPause { | |
font-size:14px | |
} | |
perspective-viewer td, perspective-viewer th { | |
color: rbga(0,0,0,0) !important; | |
font-size: 1px; | |
padding: 0; | |
overflow: hidden; | |
height: 15px !important; | |
width: 15px !important; | |
max-height: 15px !important; | |
max-width: 15px !important; | |
} | |
perspective-viewer { | |
flex: 1; | |
} | |
body { | |
background-color: rgb(46,49,54); | |
display: flex; | |
flex-direction: column; | |
position: absolute; | |
font-family: sans-serif; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
margin: 0; | |
padding: 0; | |
} | |
@media (max-width: 600px) { | |
html { | |
overflow: hidden; | |
} | |
body { | |
position: fixed; | |
height: 100%; | |
width: 100%; | |
margin: 0; | |
overflow: hidden; | |
touch-action: none; | |
} | |
} | |
</style> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Decoder } from 'https://unpkg.com/[email protected]/fastgif.js'; | |
const decoder = new Decoder(); | |
const getColorIndicesForCoord = (x, y, width) => { | |
var red = y * (width * 4) + x * 4; | |
return [red, red + 1, red + 2, red + 3]; | |
}; | |
const arrAvg = arr => arr.reduce((a,b) => a + b, 0) / arr.length; | |
export const loadGifFrames = async (gifUrl) => { | |
return window.fetch(gifUrl) | |
.then((response) => response.arrayBuffer()) | |
.then((buffer) => decoder.decode(buffer)); | |
}; | |
export const playGif = async (frames, viewer, gifUrl, imageSizePercent) => { | |
const origWidth = frames[0].imageData.width; | |
const origHeight = frames[0].imageData.height; | |
const newWidth = Math.floor(origWidth * (imageSizePercent / 100.0)); | |
const newHeight = Math.floor(origHeight * (imageSizePercent / 100.0)); | |
console.log({imageSizePercent, newWidth, newHeight}) | |
const gridSize = Math.floor(origWidth / newWidth); | |
const processedFrames = frames.map((frame, frameNum) => { | |
let indices; | |
let framePixels = []; | |
let rowPixels; | |
let averageGroup = {}; | |
for (let y = 0; y < newHeight; y++) { | |
rowPixels = { id: y }; | |
for (let x = 0; x < newWidth; x++) { | |
averageGroup = { r: [], g: [], b: [], a: [] }; | |
// super elite rockstar ninja level algorithm begins here: | |
// step one: gather all pixel data for a given grid size into an object | |
// where each key is an array of integers representing color or alpha values | |
for (let ya = 0; ya < gridSize; ya++) { | |
for (let xa = 0; xa < gridSize; xa++) { | |
indices = getColorIndicesForCoord((x * gridSize) + xa, (y * gridSize) + ya, origWidth) | |
averageGroup.r.push(frame.imageData.data[indices[0]]); | |
averageGroup.g.push(frame.imageData.data[indices[1]]); | |
averageGroup.b.push(frame.imageData.data[indices[2]]); | |
averageGroup.a.push(frame.imageData.data[indices[3]]); | |
} | |
} | |
// next, average each channel and turn it into a CSS rgba string | |
rowPixels[`x${x}`] = 'rgba(' | |
+ arrAvg(averageGroup.r) + ',' | |
+ arrAvg(averageGroup.g) + ',' | |
+ arrAvg(averageGroup.b) + ',' | |
+ arrAvg(averageGroup.a) + ')'; | |
} | |
// smoosh the row array into a frame array | |
framePixels.push(rowPixels); | |
} | |
return framePixels; | |
}); | |
const createOrUpdateTable = (pixels) => { | |
if (window.table !== undefined) { | |
window.table.update(pixels); | |
} else { | |
console.log("---create table---") | |
const worker = perspective.worker(); | |
window.table = worker.table(pixels, { index: 'id' }); | |
viewer.load(window.table); | |
} | |
}; | |
// recursively play frames until stopped | |
const playFrames = () => { | |
console.log('playFrames, current frame: ' + window.currentFrame); | |
if (window.isPlaying) { | |
createOrUpdateTable(processedFrames[window.currentFrame]); | |
window.currentFrame++; | |
if (window.currentFrame === processedFrames.length) { | |
window.currentFrame = 0; | |
} | |
setTimeout(() => { | |
playFrames(); | |
}, 1000 / window.targetPlaybackSpeed); | |
} | |
}; | |
playFrames(); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.hidden { | |
display: none; | |
} | |
#ImageControls { | |
padding: 5px 15px; | |
color: #efefef; | |
} | |
#PlayPause { | |
font-size:14px | |
} | |
perspective-viewer td, perspective-viewer th { | |
color: rbga(0,0,0,0) !important; | |
font-size: 1px; | |
padding: 0; | |
overflow: hidden; | |
height: 15px !important; | |
width: 15px !important; | |
max-height: 15px !important; | |
max-width: 15px !important; | |
} | |
perspective-viewer { | |
flex: 1; | |
} | |
body { | |
background-color: rgb(46,49,54); | |
display: flex; | |
flex-direction: column; | |
position: absolute; | |
font-family: sans-serif; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
margin: 0; | |
padding: 0; | |
} | |
@media (max-width: 600px) { | |
html { | |
overflow: hidden; | |
} | |
body { | |
position: fixed; | |
height: 100%; | |
width: 100%; | |
margin: 0; | |
overflow: hidden; | |
touch-action: none; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment