From 2bbc5f2bb3f0ef659e05c1e715624d64296e1205 Mon Sep 17 00:00:00 2001 From: John Sorrentino Date: Fri, 3 Feb 2023 14:19:42 -0600 Subject: [PATCH] first commit --- README.md | 75 +-- demo/index.css | 0 demo/index.html | 12 + demo/index.js | 14 + dist/bundle.html | 866 ------------------------ dist/examples.166ded57.js | 520 --------------- dist/examples.166ded57.js.map | 1 - dist/favicon.js | 319 --------- dist/favicon.js.map | 1 - dist/favicon.mjs | 226 ------- dist/favicon.mjs.map | 1 - dist/guitartuner.js | 204 ++++++ dist/guitartuner.js.map | 1 + dist/guitartuner.mjs | 169 +++++ dist/guitartuner.mjs.map | 1 + dist/ico.html | 806 ----------------------- dist/index.e49b5cfe.js | 1162 +++++++++++++++++++++++++++++++++ dist/index.e49b5cfe.js.map | 1 + dist/index.fb27d0e0.css | 3 + dist/index.fb27d0e0.css.map | 1 + dist/index.html | 10 +- examples/bundle.html | 105 --- examples/ico.html | 41 -- examples/index.html | 10 - examples/preview.png | Bin 35862 -> 0 bytes package.json | 30 +- src/autocorrelation.js | 78 +++ src/bundle.js | 25 - src/favicon.js | 28 - src/guitartuner.js | 72 ++ src/ico.js | 159 ----- src/png.js | 11 - src/resize.js | 35 - yarn.lock | 6 +- 34 files changed, 1746 insertions(+), 3247 deletions(-) create mode 100644 demo/index.css create mode 100644 demo/index.html create mode 100644 demo/index.js delete mode 100644 dist/bundle.html delete mode 100644 dist/examples.166ded57.js delete mode 100644 dist/examples.166ded57.js.map delete mode 100644 dist/favicon.js delete mode 100644 dist/favicon.js.map delete mode 100644 dist/favicon.mjs delete mode 100644 dist/favicon.mjs.map create mode 100644 dist/guitartuner.js create mode 100644 dist/guitartuner.js.map create mode 100644 dist/guitartuner.mjs create mode 100644 dist/guitartuner.mjs.map delete mode 100644 dist/ico.html create mode 100644 dist/index.e49b5cfe.js create mode 100644 dist/index.e49b5cfe.js.map create mode 100644 dist/index.fb27d0e0.css create mode 100644 dist/index.fb27d0e0.css.map delete mode 100644 examples/bundle.html delete mode 100644 examples/ico.html delete mode 100644 examples/index.html delete mode 100644 examples/preview.png create mode 100644 src/autocorrelation.js delete mode 100644 src/bundle.js delete mode 100644 src/favicon.js create mode 100644 src/guitartuner.js delete mode 100644 src/ico.js delete mode 100644 src/png.js delete mode 100644 src/resize.js diff --git a/README.md b/README.md index 11b4d67..9ec93d6 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,25 @@ -# Favicon.js +# guitartuner.js Favicon.js is a lightweight library that allows you to create ICO and PNG formatted favicons from a canvas element. -## Examples +## Demo ``` yarn install -yarn examples +yarn watch +yarn demo ``` -### Generate ICO +## NPM -Generate an ICO file from a `` element. Initialize a `Favicon.Ico` object with a canvas element. The canvas should be square for best results. Pass the generate method an array of sizes that the (layered) ICO file should contain. +Local package testing. -```JavaScript -const favicon = new FaviconJS(canvas); -const dataurl = favicon.ico([16, 32, 48]); ``` - -### Generate PNG - -Generate a PNG file from a `canvas` element. Initialize a `Favicon.Png` object with a canvas element. The canvas should be square for best results. Pass the generate method the size that should be generated. - -```JavaScript -const favicon = new FaviconJS(canvas); -const dataurl = favicon.png(32); +npm install ../guitartuner.js ``` -### Generate Bundle - -Generate multiple favicon format based on current best practices. +This is in a private NPM repo. -```JavaScript -const favicon = new FaviconJS(canvas); -const package = favicon.bundle(); ``` - -The bundle will contain the follow keys which map to common favicon formats. - -- `ico` - favicon.ico -- `png16` - favicon-16x16.png -- `png32` - favicon-32x32.png -- `png180` - apple-touch-icon.png -- `png192` - android-chrome-192x192.png -- `png512` - android-chrome-512x512.png - -### Example - -The example below will generate an ICO formatted favicon that includes 3 sizes: 16x16, 32x32, and 48x48 pixels. The full example can be found [here](./examples/ico.html). - -![Preview](./examples/preview.png) - -```JavaScript -// Setup canvas -const canvas = document.getElementById("canvas"); -canvas.width = 128; -canvas.height = 128; -const context = canvas.getContext("2d"); - -// Draw background -context.fillStyle = "#d85537"; -context.fillRect(0, 0, canvas.width, canvas.height); - -// Draw text -context.fillStyle = "#FFFFFF"; -context.font = "100px Helvetica"; -context.textBaseline = "middle"; -context.textAlign = "center"; -const x = canvas.width / 2; -const y = canvas.height / 2; -context.fillText("F", x, y); - -// Create favicon.ico dataurl -const favicon = new FaviconJS(canvas); -const dataurl = favicon.ico([16, 32, 48]); - -// Activate the download button -const download = document.getElementById("download"); -download.href = dataurl; -download.setAttribute("download", "favicon.ico"); +npm publish ``` diff --git a/demo/index.css b/demo/index.css new file mode 100644 index 0000000..e69de29 diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..0266c0f --- /dev/null +++ b/demo/index.html @@ -0,0 +1,12 @@ + + + + + + GuitarTunerJS + + + + + + diff --git a/demo/index.js b/demo/index.js new file mode 100644 index 0000000..f3f2a74 --- /dev/null +++ b/demo/index.js @@ -0,0 +1,14 @@ +import GuitarTuner from "../dist/guitartuner.mjs"; + +const guitarTuner = new GuitarTuner(); +const startButton = document.getElementById("start"); +const stopButton = document.getElementById("stop"); +startButton.addEventListener("click", () => { + guitarTuner.start(); +}); +stopButton.addEventListener("click", () => { + guitarTuner.stop(); +}); +guitarTuner.setCallback(function (frequency, note) { + console.log(frequency, note); +}); diff --git a/dist/bundle.html b/dist/bundle.html deleted file mode 100644 index 21a096a..0000000 --- a/dist/bundle.html +++ /dev/null @@ -1,866 +0,0 @@ - - - - FaviconJS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeSizePreview
canvas element128x128 - -
favicon.ico16, 32, 48 - -
android-chrome-192x192.png192x192 - -
android-chrome-512x512.png512x512 - -
apple-touch-icon.png180x180 - -
favicon-32x32.png32x32 - -
favicon-16x16.png16x16 - -
- - - diff --git a/dist/examples.166ded57.js b/dist/examples.166ded57.js deleted file mode 100644 index 1ebdf6d..0000000 --- a/dist/examples.166ded57.js +++ /dev/null @@ -1,520 +0,0 @@ -// modules are defined as an array -// [ module function, map of requires ] -// -// map of requires is short require name -> numeric require -// -// anything defined in a previous bundle is accessed via the -// orig method which is the require for previous bundles - -(function (modules, entry, mainEntry, parcelRequireName, globalName) { - /* eslint-disable no-undef */ - var globalObject = - typeof globalThis !== 'undefined' - ? globalThis - : typeof self !== 'undefined' - ? self - : typeof window !== 'undefined' - ? window - : typeof global !== 'undefined' - ? global - : {}; - /* eslint-enable no-undef */ - - // Save the require from previous bundle to this closure if any - var previousRequire = - typeof globalObject[parcelRequireName] === 'function' && - globalObject[parcelRequireName]; - - var cache = previousRequire.cache || {}; - // Do not use `require` to prevent Webpack from trying to bundle this call - var nodeRequire = - typeof module !== 'undefined' && - typeof module.require === 'function' && - module.require.bind(module); - - function newRequire(name, jumped) { - if (!cache[name]) { - if (!modules[name]) { - // if we cannot find the module within our internal map or - // cache jump to the current global require ie. the last bundle - // that was added to the page. - var currentRequire = - typeof globalObject[parcelRequireName] === 'function' && - globalObject[parcelRequireName]; - if (!jumped && currentRequire) { - return currentRequire(name, true); - } - - // If there are other bundles on this page the require from the - // previous one is saved to 'previousRequire'. Repeat this as - // many times as there are bundles until the module is found or - // we exhaust the require chain. - if (previousRequire) { - return previousRequire(name, true); - } - - // Try the node require function if it exists. - if (nodeRequire && typeof name === 'string') { - return nodeRequire(name); - } - - var err = new Error("Cannot find module '" + name + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; - } - - localRequire.resolve = resolve; - localRequire.cache = {}; - - var module = (cache[name] = new newRequire.Module(name)); - - modules[name][0].call( - module.exports, - localRequire, - module, - module.exports, - this - ); - } - - return cache[name].exports; - - function localRequire(x) { - var res = localRequire.resolve(x); - return res === false ? {} : newRequire(res); - } - - function resolve(x) { - var id = modules[name][1][x]; - return id != null ? id : x; - } - } - - function Module(moduleName) { - this.id = moduleName; - this.bundle = newRequire; - this.exports = {}; - } - - newRequire.isParcelRequire = true; - newRequire.Module = Module; - newRequire.modules = modules; - newRequire.cache = cache; - newRequire.parent = previousRequire; - newRequire.register = function (id, exports) { - modules[id] = [ - function (require, module) { - module.exports = exports; - }, - {}, - ]; - }; - - Object.defineProperty(newRequire, 'root', { - get: function () { - return globalObject[parcelRequireName]; - }, - }); - - globalObject[parcelRequireName] = newRequire; - - for (var i = 0; i < entry.length; i++) { - newRequire(entry[i]); - } - - if (mainEntry) { - // Expose entry point to Node, AMD or browser globals - // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js - var mainExports = newRequire(mainEntry); - - // CommonJS - if (typeof exports === 'object' && typeof module !== 'undefined') { - module.exports = mainExports; - - // RequireJS - } else if (typeof define === 'function' && define.amd) { - define(function () { - return mainExports; - }); - - // - - diff --git a/dist/index.e49b5cfe.js b/dist/index.e49b5cfe.js new file mode 100644 index 0000000..de1256c --- /dev/null +++ b/dist/index.e49b5cfe.js @@ -0,0 +1,1162 @@ +// modules are defined as an array +// [ module function, map of requires ] +// +// map of requires is short require name -> numeric require +// +// anything defined in a previous bundle is accessed via the +// orig method which is the require for previous bundles + +(function (modules, entry, mainEntry, parcelRequireName, globalName) { + /* eslint-disable no-undef */ + var globalObject = + typeof globalThis !== 'undefined' + ? globalThis + : typeof self !== 'undefined' + ? self + : typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; + /* eslint-enable no-undef */ + + // Save the require from previous bundle to this closure if any + var previousRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + + var cache = previousRequire.cache || {}; + // Do not use `require` to prevent Webpack from trying to bundle this call + var nodeRequire = + typeof module !== 'undefined' && + typeof module.require === 'function' && + module.require.bind(module); + + function newRequire(name, jumped) { + if (!cache[name]) { + if (!modules[name]) { + // if we cannot find the module within our internal map or + // cache jump to the current global require ie. the last bundle + // that was added to the page. + var currentRequire = + typeof globalObject[parcelRequireName] === 'function' && + globalObject[parcelRequireName]; + if (!jumped && currentRequire) { + return currentRequire(name, true); + } + + // If there are other bundles on this page the require from the + // previous one is saved to 'previousRequire'. Repeat this as + // many times as there are bundles until the module is found or + // we exhaust the require chain. + if (previousRequire) { + return previousRequire(name, true); + } + + // Try the node require function if it exists. + if (nodeRequire && typeof name === 'string') { + return nodeRequire(name); + } + + var err = new Error("Cannot find module '" + name + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + + localRequire.resolve = resolve; + localRequire.cache = {}; + + var module = (cache[name] = new newRequire.Module(name)); + + modules[name][0].call( + module.exports, + localRequire, + module, + module.exports, + this + ); + } + + return cache[name].exports; + + function localRequire(x) { + var res = localRequire.resolve(x); + return res === false ? {} : newRequire(res); + } + + function resolve(x) { + var id = modules[name][1][x]; + return id != null ? id : x; + } + } + + function Module(moduleName) { + this.id = moduleName; + this.bundle = newRequire; + this.exports = {}; + } + + newRequire.isParcelRequire = true; + newRequire.Module = Module; + newRequire.modules = modules; + newRequire.cache = cache; + newRequire.parent = previousRequire; + newRequire.register = function (id, exports) { + modules[id] = [ + function (require, module) { + module.exports = exports; + }, + {}, + ]; + }; + + Object.defineProperty(newRequire, 'root', { + get: function () { + return globalObject[parcelRequireName]; + }, + }); + + globalObject[parcelRequireName] = newRequire; + + for (var i = 0; i < entry.length; i++) { + newRequire(entry[i]); + } + + if (mainEntry) { + // Expose entry point to Node, AMD or browser globals + // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js + var mainExports = newRequire(mainEntry); + + // CommonJS + if (typeof exports === 'object' && typeof module !== 'undefined') { + module.exports = mainExports; + + // RequireJS + } else if (typeof define === 'function' && define.amd) { + define(function () { + return mainExports; + }); + + // + GuitarTunerJS - Ico - Bundle - + + + diff --git a/examples/bundle.html b/examples/bundle.html deleted file mode 100644 index 07f219e..0000000 --- a/examples/bundle.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - FaviconJS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeSizePreview
canvas element128x128 - -
favicon.ico16, 32, 48 - -
android-chrome-192x192.png192x192 - -
android-chrome-512x512.png512x512 - -
apple-touch-icon.png180x180 - -
favicon-32x32.png32x32 - -
favicon-16x16.png16x16 - -
- - - diff --git a/examples/ico.html b/examples/ico.html deleted file mode 100644 index b362149..0000000 --- a/examples/ico.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - FaviconJS - - - - Download - - - diff --git a/examples/index.html b/examples/index.html deleted file mode 100644 index 5a97d3c..0000000 --- a/examples/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - FaviconJS - - - Ico - Bundle - - diff --git a/examples/preview.png b/examples/preview.png deleted file mode 100644 index b00cf1870e5cf140b754ef99a3b01204de6e0e5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35862 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 diff --git a/package.json b/package.json index a28ce9a..d34714b 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "@johnsorrentino/favicon.js", - "description": "Favicon.js is a lightweight library that allows you to create an ICO formatted favicon from a canvas element.", + "name": "@johnsorrentino/guitartuner.js", + "description": "guitartuner.js is a lightweight library that leverages your microphone to help tune your guitar.", "version": "0.0.1", "author": "johnsorrentino", "browserslist": "> 0.5%, last 2 versions, not dead", "bugs": { - "url": "https://github.com/johnsorrentino/faviconjs/issues" + "url": "https://github.com/johnsorrentino/guitartunerjs/issues" }, "dependencies": { "@swc/helpers": "^0.3.8" @@ -13,29 +13,19 @@ "devDependencies": { "parcel": "latest" }, - "homepage": "https://github.com/johnsorrentino/faviconjs#readme", - "keywords": [ - "bookmark icon", - "favicon", - "favorite icon", - "ico", - "icon", - "shortcut icon", - "tab icon", - "url icon", - "website icon" - ], + "homepage": "https://github.com/johnsorrentino/guitartunerjs#readme", + "keywords": [], "license": "MIT", - "main": "dist/favicon.js", - "module": "dist/favicon.mjs", + "main": "dist/guitartuner.js", + "module": "dist/guitartuner.mjs", "repository": { "type": "git", - "url": "git+https://github.com/johnsorrentino/faviconjs.git" + "url": "git+https://github.com/johnsorrentino/guitartunerjs.git" }, "scripts": { "build": "parcel build", - "examples": "parcel examples/index.html", + "demo": "parcel demo/index.html", "watch": "parcel watch" }, - "source": "src/favicon.js" + "source": "src/guitartuner.js" } diff --git a/src/autocorrelation.js b/src/autocorrelation.js new file mode 100644 index 0000000..ab65b78 --- /dev/null +++ b/src/autocorrelation.js @@ -0,0 +1,78 @@ +class Autocorrelation { + constructor(buffer, sampleRate) { + this.buffer = buffer; + this.sampleRate = sampleRate; + } + + // Using the strategy defined here. + // https://github.com/cwilso/PitchDetect/pull/23 + execute() { + // Reject if RMS is too small. + const rms = this.#rms(this.buffer); + if (rms < 0.05) return -1; + + // Trim the buffer so that it starts and ends at "zero". + const startPosition = this.#startPosition(this.buffer, 0.2); + const endPosition = this.#endPosition(this.buffer, 0.2); + this.buffer = this.buffer.slice(startPosition, endPosition); + + // Take the dot product of the offsets + var c = new Array(this.buffer.length).fill(0); + for (var i = 0; i < this.buffer.length; i++) + for (var j = 0; j < this.buffer.length - i; j++) + c[i] = c[i] + this.buffer[j] * this.buffer[j + i]; + + // Find the maximum value and its position + var d = 0; + while (c[d] > c[d + 1]) d++; + var maxval = -1, + maxpos = -1; + for (var i = d; i < this.buffer.length; i++) { + if (c[i] > maxval) { + maxval = c[i]; + maxpos = i; + } + } + var T0 = maxpos; + + // Some type of smoothing + // interpolation is parabolic interpolation. It helps with precision. We suppose that a parabola pass through the three points that comprise the peak. 'a' and 'b' are the unknowns from the linear equation system and b/(2a) is the "error" in the abscissa. Well x1,x2,x3 should be y1,y2,y3 because they are the ordinates. + var x1 = c[T0 - 1], + x2 = c[T0], + x3 = c[T0 + 1]; + const a = (x1 + x3 - 2 * x2) / 2; + const b = (x3 - x1) / 2; + if (a) T0 = T0 - b / (2 * a); + + // Return the frequency / pitch + return this.sampleRate / T0; + } + + // Calculates and returns the root mean square of the array. + #rms(array) { + const squares = array.map((value) => value * value); + const sum = squares.reduce((accumulator, value) => accumulator + value); + const mean = sum / array.length; + return Math.sqrt(mean); + } + + // Finds and returns the first near zero within the array. + #startPosition(array, threshold) { + for (var i = 0; i < array.length / 2; i++) { + if (Math.abs(array[i]) < threshold) { + return i; + } + } + } + + // Finds and returns the last near zero within the array. + #endPosition(array, threshold) { + for (var i = 1; i < array.length / 2; i++) { + if (Math.abs(array[array.length - i]) < threshold) { + return array.length - i; + } + } + } +} + +export default Autocorrelation; diff --git a/src/bundle.js b/src/bundle.js deleted file mode 100644 index 2195bc1..0000000 --- a/src/bundle.js +++ /dev/null @@ -1,25 +0,0 @@ -import Ico from "./ico.js"; -import Png from "./png.js"; - -class Bundle { - constructor(canvas) { - this.canvas = canvas; - } - - generate() { - const ico = new Ico(this.canvas); - const png = new Png(this.canvas); - - return { - ico: ico.generate([16, 32, 48]), - png16: png.generate(16), - png32: png.generate(32), - png150: png.generate(150), - png180: png.generate(180), - png192: png.generate(192), - png512: png.generate(512), - }; - } -} - -export default Bundle; diff --git a/src/favicon.js b/src/favicon.js deleted file mode 100644 index 42472af..0000000 --- a/src/favicon.js +++ /dev/null @@ -1,28 +0,0 @@ -import Bundle from "./bundle"; -import Ico from "./ico.js"; -import Png from "./png"; -import Resize from "./resize"; - -class FaviconJS { - constructor(canvas) { - this.canvas = canvas; - } - - bundle() { - return new Bundle(this.canvas).generate(); - } - - ico(sizes) { - return new Ico(this.canvas).generate(sizes); - } - - png(size) { - return new Png(this.canvas).generate(size); - } - - resize(size) { - return new Resize(this.canvas).generate(size, size); - } -} - -export default FaviconJS; diff --git a/src/guitartuner.js b/src/guitartuner.js new file mode 100644 index 0000000..1647aca --- /dev/null +++ b/src/guitartuner.js @@ -0,0 +1,72 @@ +import Autocorrelation from "./autocorrelation"; + +class GuitarTuner { + #stream; + #audioContext; + #mediaStreamSource; + #analyser; + #fft_size = 2048; + #notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; + + start() { + this.#requestMicrophoneAccess(); + } + + stop() { + const recorder = new MediaRecorder(this.#stream); + recorder.stream.getAudioTracks().forEach(function (track) { + track.stop(); + }); + } + + setCallback(callback) { + this.callback = callback; + } + + #requestMicrophoneAccess() { + navigator.mediaDevices + .getUserMedia({ video: false, audio: true }) + .then((stream) => { + this.#stream = stream; + this.#createAnalyser(); + this.#draw(); + }) + .catch((error) => { + console.log(error); + }); + } + + #createAnalyser() { + this.#audioContext = new AudioContext(); + this.#mediaStreamSource = this.#audioContext.createMediaStreamSource( + this.#stream + ); + this.#analyser = this.#audioContext.createAnalyser(); + this.#analyser.fftSize = this.#fft_size; + this.#mediaStreamSource.connect(this.#analyser); + } + + #draw() { + requestAnimationFrame(() => { + this.#draw(); + }); + + const bufferLength = this.#analyser.fftSize; + const buffer = new Float32Array(bufferLength); + this.#analyser.getFloatTimeDomainData(buffer); + const sampleRate = this.#audioContext.sampleRate; + const autocorrelation = new Autocorrelation(buffer, sampleRate); + const frequency = autocorrelation.execute(); + if (frequency === -1) return; + + const note = this.#frequencyToNote(frequency); + if (typeof this.callback === "function") this.callback(frequency, note); + } + + #frequencyToNote(frequency) { + var noteNum = 12 * (Math.log(frequency / 440) / Math.log(2)); + return this.#notes[(Math.round(noteNum) + 69) % 12]; + } +} + +export default GuitarTuner; diff --git a/src/ico.js b/src/ico.js deleted file mode 100644 index 1339da2..0000000 --- a/src/ico.js +++ /dev/null @@ -1,159 +0,0 @@ -import Resize from "./resize.js"; - -class Ico { - constructor(canvas) { - this.canvas = canvas; - } - - generate(sizes = [16, 32, 48]) { - const canvasMaster = new Resize(this.canvas).generate(128, 128); - const iconDirectoryHeader = this.createIconDirectoryHeader(sizes.length); - let iconDirectoryEntries = ""; - let bitmapData = ""; - - for (let i = 0; i < sizes.length; i++) { - const size = sizes[i]; - const canvas = new Resize(canvasMaster).generate(size, size); - const context = canvas.getContext("2d"); - const width = canvas.width; - const height = canvas.height; - const imageData = context.getImageData(0, 0, width, height); - const bitmapInfoHeader = this.createBitmapInfoHeader(width, height); - const bitmapImageData = this.createBitmapImageData(canvas); - const bitmapSize = bitmapInfoHeader.length + bitmapImageData.length; - const bitmapOffset = this.calculateBitmapOffset(sizes, i); - iconDirectoryEntries += this.createIconDirectoryEntry( - width, - height, - bitmapSize, - bitmapOffset - ); - bitmapData += bitmapInfoHeader + bitmapImageData; - } - - const binary = iconDirectoryHeader + iconDirectoryEntries + bitmapData; - const base64 = "data:image/x-icon;base64," + btoa(binary); - return base64; - } - - /** - * Calculates the location to the bitmap entry. - */ - calculateBitmapOffset(sizes, entry) { - let offset = 6; // icon header size - offset += 16 * sizes.length; // icon entry header size - - // size of previous bitmaps - for (let i = 0; i < entry; i++) { - const size = sizes[i]; - offset += 40; // bitmap header size - offset += 4 * size * size; // bitmap data size - offset += (2 * size * size) / 8; // bitmap mask size - } - return offset; - } - - createBitmapImageData(canvas) { - const ctx = canvas.getContext("2d"); - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const bitmapPixelData = new Uint32Array(imageData.data.buffer); - const bitmapBuffer = bitmapPixelData.reverse().buffer; - const bitmapMask = new Uint8Array((canvas.width * canvas.height * 2) / 8); - bitmapMask.fill(0); - let binary = this.arrayBufferToBinary(this.canvasToBitmap(canvas)); - binary += this.Uint8ArrayToBinary(bitmapMask); - return binary; - } - - canvasToBitmap(canvas) { - const ctx = canvas.getContext("2d"); - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const rgbaData8 = imageData.data; - const bgraData8 = new Uint8ClampedArray(imageData.data.length); - for (let i = 0; i < rgbaData8.length; i += 4) { - const r = rgbaData8[i]; - const g = rgbaData8[i + 1]; - const b = rgbaData8[i + 2]; - const a = rgbaData8[i + 3]; - bgraData8[i] = b; - bgraData8[i + 1] = g; - bgraData8[i + 2] = r; - bgraData8[i + 3] = a; - } - - const bgraData32 = new Uint32Array(bgraData8.buffer); - const bgraData32Rotated = new Uint32Array(bgraData32.length); - for (let i = 0; i < bgraData32.length; i++) { - const xPos = i % canvas.width; - const yPos = Math.floor(i / canvas.width); - const xPosRotated = xPos; - const yPosRotated = canvas.height - 1 - yPos; - const indexRotated = yPosRotated * canvas.width + xPosRotated; - const pixel = bgraData32[i]; - bgraData32Rotated[indexRotated] = pixel; - } - return bgraData32Rotated.buffer; - } - - createIconDirectoryHeader(numImages) { - const buffer = new ArrayBuffer(6); - const view = new DataView(buffer); - view.setUint16(0, 0, true); // Reserved. Must always be 0. - view.setUint16(2, 1, true); // Specifies type. 1 = ICO. - view.setUint16(4, numImages, true); // Number of images. - return this.arrayBufferToBinary(buffer); - } - - createIconDirectoryEntry(width, height, size, offset) { - const buffer = new ArrayBuffer(16); - const view = new DataView(buffer); - view.setUint8(0, width); // Pixel width (0..256). 0 = 256 pixels. - view.setUint8(1, height); // Pixel height (0..256). 0 = 256 pixels. - view.setUint8(2, 0); // Number of colors in pallet. 0 = no pallet. - view.setUint8(3, 0); // Reserved. Should be 0. - view.setUint16(4, 1, true); // Color planes. 0 or 1. - view.setUint16(6, 32, true); // Specifies bits per pixel. - view.setUint32(8, size, true); // Image size (bytes). - view.setUint32(12, offset, true); // Offset to BMP of PNG. - return this.arrayBufferToBinary(buffer); - } - - createBitmapInfoHeader(width, height) { - const buffer = new ArrayBuffer(40); - const view = new DataView(buffer); - view.setUint32(0, 40, true); // Header size (40 bytes). - view.setInt32(4, width, true); // BMP width. - view.setInt32(8, 2 * height, true); // BMP height. - view.setUint16(12, 1, true); // Number of color planes. Must be 1. - view.setUint16(14, 32, true); // Bits per pixel - view.setUint32(16, 0, true); // Compression method. 0 = none. - view.setUint32(20, 0, true); // Image size (bytes). 0 = no compression. - view.setUint32(24, 0, true); // Horizontal resolution. - view.setUint32(28, 0, true); // Vertical resolution. - view.setUint32(32, 0, true); // Number of colors. 0 = default. - view.setUint32(36, 0, true); // Number of important colors. 0 = all - return this.arrayBufferToBinary(buffer); - } - - arrayBufferToBinary(buffer) { - let binary = ""; - const bytes = new Uint8Array(buffer); - const len = bytes.byteLength; - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); - } - return binary; - } - - Uint8ArrayToBinary(Uint8Array) { - let binary = ""; - const bytes = Uint8Array; - const len = bytes.byteLength; - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); - } - return binary; - } -} - -export default Ico; diff --git a/src/png.js b/src/png.js deleted file mode 100644 index a61ea89..0000000 --- a/src/png.js +++ /dev/null @@ -1,11 +0,0 @@ -import Resize from "./resize"; - -export default class Png { - constructor(canvas) { - this.canvas = canvas; - } - - generate(size) { - return new Resize(this.canvas).generate(size, size).toDataURL(); - } -} diff --git a/src/resize.js b/src/resize.js deleted file mode 100644 index e6bf228..0000000 --- a/src/resize.js +++ /dev/null @@ -1,35 +0,0 @@ -class Resize { - constructor(canvas) { - this.canvas = canvas; - } - - /** - * Resize the canvas by halving the width and height. This produces better - * sampling and the image quality is generally better. - */ - generate(width, height) { - while (this.canvas.width / 2 >= width) { - this._resize(this.canvas.width / 2, this.canvas.height / 2); - } - - if (this.canvas.width > width) { - this._resize(width, height); - } - - return this.canvas; - } - - /** - * Simple resize of a canvas element. - */ - _resize(width, height) { - let canvas = document.createElement("canvas"); - let resizedContext = canvas.getContext("2d"); - canvas.width = width; - canvas.height = height; - resizedContext.drawImage(this.canvas, 0, 0, width, height); - this.canvas = canvas; - } -} - -export default Resize; diff --git a/yarn.lock b/yarn.lock index 546dc2d..58e7515 100644 --- a/yarn.lock +++ b/yarn.lock @@ -708,9 +708,9 @@ callsites@^3.0.0: integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== caniuse-lite@^1.0.30001317: - version "1.0.30001327" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz#c1546d7d7bb66506f0ccdad6a7d07fc6d668c858" - integrity sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w== + version "1.0.30001450" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz" + integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== chalk@^2.0.0: version "2.4.2"