Creating Your Own Image Format In The Browser

A presentation at CityJS in May 2019 in London, UK by Trent Willis

Slide 1

Slide 1

CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

Slide 2

Slide 2

ATWOOD’S LAW “ANY APPLICATION THAT CAN BE WRITTEN IN JAVASCRIPT, WILL EVENTUALLY BE WRITTEN IN JAVASCRIPT.”

Slide 3

Slide 3

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

Slide 8

Slide 8

CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

Slide 9

Slide 9

@TRENTMWILLIS

Slide 10

Slide 10

LOVE THE WEB LOVE JAVASCRIPT @TRENTMWILLIS #CityJSConf

Slide 11

Slide 11

JAVASCRIPT CONTINUES TO EVOLVE @TRENTMWILLIS #CityJSConf

Slide 12

Slide 12

WEB HYPERTEXT APPLICATION TECHNOLOGY WORKING GROUP @TRENTMWILLIS #CityJSConf

Slide 13

Slide 13

WHATWG @TRENTMWILLIS #CityJSConf

Slide 14

Slide 14

WHATWG STREAMS SPEC @TRENTMWILLIS #CityJSConf

Slide 15

Slide 15

STREAMS SPEC “IF INSTALLED INSIDE THE FETCH HOOK OF A SERVICE WORKER, THIS WOULD ALLOW DEVELOPERS TO TRANSPARENTLY POLYFILL NEW IMAGE FORMATS.” @TRENTMWILLIS #CityJSConf

Slide 16

Slide 16

@TRENTMWILLIS #CityJSConf

Slide 17

Slide 17

@TRENTMWILLIS #CityJSConf

Slide 18

Slide 18

PHOTOS ARE IMPORTANT @TRENTMWILLIS #CityJSConf

Slide 19

Slide 19

PHOTOS CAN BE EXPERIMENTED WITH @TRENTMWILLIS #CityJSConf

Slide 20

Slide 20

WEB CRYPTO API @TRENTMWILLIS #CityJSConf

Slide 21

Slide 21

@TRENTMWILLIS #CityJSConf

Slide 22

Slide 22

CRYPTO + INSTAGRAM @TRENTMWILLIS #CityJSConf

Slide 23

Slide 23

CRYPTOGRAM @TRENTMWILLIS #CityJSConf

Slide 24

Slide 24

Encrypted Image @TRENTMWILLIS #CityJSConf

Slide 25

Slide 25

Encrypted Image <img src=“./encrypted-image.epng”> @TRENTMWILLIS #CityJSConf

Slide 26

Slide 26

Encrypted Image <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #CityJSConf

Slide 27

Slide 27

Encrypted Image Service Worker <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #CityJSConf

Slide 28

Slide 28

Encrypted Image .epng Service Worker .png <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #CityJSConf

Slide 29

Slide 29

ALL CODE WILL BE AVAILABLE ONLINE @TRENTMWILLIS #CityJSConf

Slide 30

Slide 30

FETCHEVENT @TRENTMWILLIS #CityJSConf

Slide 31

Slide 31

FETCHEVENT .RESPONDWITH() @TRENTMWILLIS #CityJSConf

Slide 32

Slide 32

navigator.serviceWorker.register(‘./service-worker.js’); @TRENTMWILLIS #CityJSConf

Slide 33

Slide 33

self.addEventListener(‘fetch’, (event) => { }); @TRENTMWILLIS #CityJSConf

Slide 34

Slide 34

self.addEventListener(‘fetch’, (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { } }); @TRENTMWILLIS #CityJSConf

Slide 35

Slide 35

const isEncryptedImageRequest = request => request.url.endsWith(‘.epng’); @TRENTMWILLIS #CityJSConf

Slide 36

Slide 36

self.addEventListener(‘fetch’, (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { }; }); @TRENTMWILLIS #CityJSConf

Slide 37

Slide 37

self.addEventListener(‘fetch’, (event) => { if (isEncryptedImageRequest(event.request)) { event.respondWith(pngFromEncryptedImageRequest(event.request)); pngFromEncryptedImageRequest }; }); @TRENTMWILLIS #CityJSConf

Slide 38

Slide 38

const pngFromEncryptedImageRequest = async (request) => {; }; @TRENTMWILLIS #CityJSConf

Slide 39

Slide 39

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); }; @TRENTMWILLIS #CityJSConf

Slide 40

Slide 40

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const encryptedData = await response.arrayBuffer(); }; @TRENTMWILLIS #CityJSConf

Slide 41

Slide 41

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const encryptedData = await response.arrayBuffer(); const decryptedData = await decryptData(encryptedData); }; @TRENTMWILLIS #CityJSConf

Slide 42

Slide 42

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: ‘image/png’ }); }; @TRENTMWILLIS #CityJSConf

Slide 43

Slide 43

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 44

Slide 44

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 45

Slide 45

const pngFromCustomImageRequest = async (request, transform) => {; const const const const response = await fetch(request); rawData = await response.arrayBuffer(); pngData = await transform(rawData); pngBlob = new Blob([pngData], {;type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 46

Slide 46

IT’S BREAKING JUST AS EXPECTED @TRENTMWILLIS #CityJSConf

Slide 47

Slide 47

WEB CRYPTO API @TRENTMWILLIS #CityJSConf

Slide 48

Slide 48

SUBTLECRYPTO @TRENTMWILLIS #CityJSConf

Slide 49

Slide 49

SUBTLECRYPTO “IT IS NAMED SUBTLECRYPTO TO REFLECT THE FACT THAT MANY OF THESE ALGORITHMS HAVE SUBTLE USAGE REQUIREMENTS IN ORDER TO PROVIDE THE REQUIRED ALGORITHMIC SECURITY GUARANTEES.” @TRENTMWILLIS #CityJSConf

Slide 50

Slide 50

CRYPTOKEY @TRENTMWILLIS #CityJSConf

Slide 51

Slide 51

const pngFromEncryptedImageRequest = async (request) => { const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 52

Slide 52

const decryptData = async (encryptedData) => {; }; @TRENTMWILLIS #CityJSConf

Slide 53

Slide 53

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); };; @TRENTMWILLIS #CityJSConf

Slide 54

Slide 54

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); const decryptionOptions = {; name: ‘AES-CTR’, counter: new Uint8Array(16), length: 128 }; };; @TRENTMWILLIS #CityJSConf

Slide 55

Slide 55

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); const decryptionOptions = {; name: ‘AES-CTR’, counter: new Uint8Array(16), length: 128 }; return crypto.subtle.decrypt( decryptionOptions, cryptoKey, encryptedData ); };; @TRENTMWILLIS #CityJSConf

Slide 56

Slide 56

const pngFromEncryptedImageRequest = async (request) => { const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 57

Slide 57

CRYPTOGRAM-NAIVE.GLITCH.ME @TRENTMWILLIS #CityJSConf

Slide 58

Slide 58

MAKE IT WORK MAKE IT RIGHT MAKE IT FAST @TRENTMWILLIS #CityJSConf

Slide 59

Slide 59

Encrypted Data @TRENTMWILLIS #CityJSConf

Slide 60

Slide 60

Encrypted Data @TRENTMWILLIS Decrypted Data #CityJSConf

Slide 61

Slide 61

BATCH Batch digra PROCESSING Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 62

Slide 62

STREAM PROCESSING Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 63

Slide 63

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 64

Slide 64

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 65

Slide 65

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 66

Slide 66

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 67

Slide 67

Encrypted Data Encrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Decrypted Data Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #CityJSConf

Slide 68

Slide 68

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: ‘image/png’ }); return new Response(pngBlob); }; @TRENTMWILLIS #CityJSConf

Slide 69

Slide 69

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 70

Slide 70

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 71

Slide 71

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 72

Slide 72

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 73

Slide 73

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 74

Slide 74

WRITABLESTREAM @TRENTMWILLIS #CityJSConf

Slide 75

Slide 75

WRITABLESTREAM READABLESTREAM @TRENTMWILLIS #CityJSConf

Slide 76

Slide 76

WRITABLESTREAM + READABLESTREAM = TRANSFORMSTREAM @TRENTMWILLIS #CityJSConf

Slide 77

Slide 77

TRANSFORMER @TRENTMWILLIS #CityJSConf

Slide 78

Slide 78

TRANSFORMER .START() .TRANSFORM() .FLUSH() @TRENTMWILLIS #CityJSConf

Slide 79

Slide 79

TRANSFORMER .START() .TRANSFORM() .FLUSH() @TRENTMWILLIS #CityJSConf

Slide 80

Slide 80

TRANSFORMER .START() .TRANSFORM() .FLUSH() @TRENTMWILLIS #CityJSConf

Slide 81

Slide 81

TRANSFORMER .START() .TRANSFORM() .FLUSH() @TRENTMWILLIS #CityJSConf

Slide 82

Slide 82

const pngFromEncryptedImageRequest = async (request) => { const response = await fetch(request); const decrypter = new TransformStream(new Decrypter Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #CityJSConf

Slide 83

Slide 83

class Decrypter {; async start() {; }; async transform() {; }; async flush() {; }; }; @TRENTMWILLIS #CityJSConf

Slide 84

Slide 84

class Decrypter {; async start() {; this.counter = new Uint8Array(16); }; async transform() {; }; async flush() {; }; }; @TRENTMWILLIS #CityJSConf

Slide 85

Slide 85

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() {; }; };; async flush() {; }; }; @TRENTMWILLIS #CityJSConf

Slide 86

Slide 86

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); };; @TRENTMWILLIS #CityJSConf

Slide 87

Slide 87

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; };; @TRENTMWILLIS #CityJSConf

Slide 88

Slide 88

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); const remainder = length % 16; if (remainder) { this.remainderData = data.slice(length - remainder); data = data.slice(0, length - remainder); }; const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; }; @TRENTMWILLIS #CityJSConf

Slide 89

Slide 89

async transform( transform(chunk, controller) ) {; let data = chunk; if (this.remainderData) { data = concatUint8Arrays(this.remainderData, data); this.remainderData = null; } const length = data.length; const blocks = Math.floor(length / 16); const remainder = length % 16; if (remainder) { this.remainderData = data.slice(length - remainder); data = data.slice(0, length - remainder); }; const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; }; @TRENTMWILLIS #CityJSConf

Slide 90

Slide 90

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() { {; // It’s too long! }; async flush() {; }; }; @TRENTMWILLIS #CityJSConf

Slide 91

Slide 91

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() {; // It’s too long! }; async flush(controller) {; if (this.remainderData) { const decryptedData = await decrypt( this.key, this.remainderData, this.counter); controller.enqueue(decryptedData); } }; }; @TRENTMWILLIS #CityJSConf

Slide 92

Slide 92

CRYPTOGRAM-STREAMING.GLITCH.ME @TRENTMWILLIS #CityJSConf

Slide 93

Slide 93

WEBASSEMBLY FOR LIGHTNING FAST TRANSFORM STREAMS wasm-streams.glitch.me @TRENTMWILLIS #CityJSConf

Slide 94

Slide 94

RUNTIME STREAMING COMPILATION FOR DEVELOPMENT @TRENTMWILLIS #CityJSConf

Slide 95

Slide 95

HTML MODULE POLYFILL html-modules-polyfill.glitch.me @TRENTMWILLIS #CityJSConf

Slide 96

Slide 96

PACKAGE NAME MAPS POLYFILL package-name-maps.glitch.me @TRENTMWILLIS #CityJSConf

Slide 97

Slide 97

RUNTIME VIDEO EFFECTS @TRENTMWILLIS #CityJSConf

Slide 98

Slide 98

RUNTIME STREAMING SERVICE WORKERS COMPILATION FOR DEVELOPMENT STREAMS HTML MODULE POLYFILL WEB CRYPTO PACKAGE NAME MAPS POLYFILL ESNEXT RUNTIME VIDEO EFFECTS @TRENTMWILLIS WEB ASSEMBLY WEB COMPONENTS AND MORE! #CityJSConf

Slide 99

Slide 99

WRITE EVERYTHING IN JAVASCRIPT! * *WITHIN REASON, BUT DEFINITELY HAVE FUN! **ALSO, GLITCH IS GREAT FOR EXPERIMENTS! @TRENTMWILLIS #CityJSConf