Utils.js (5427B)
1 import base64js from 'base64-js' 2 import omggif from 'omggif' 3 4 /** 5 * Converts RGB array to named object 6 * @param {[]} item, a length 3 array containing 8 bit RGB value 7 */ 8 function rgb(item) { 9 return { 10 r: item[0], 11 g: item[1], 12 b: item[2] 13 } 14 } 15 16 /** 17 * Converts XY array to named object 18 * @param {[]} item, a 2 length array containing x and y values 19 */ 20 function xy(item) { 21 return { 22 x: item[0], 23 y: item[1] 24 } 25 } 26 27 /** 28 * Compares colors. 29 * Colors are either 3-element arrays [r, g, b] 30 * Or objects with .r .g .b members 31 */ 32 function colorEqual(item1, item2) { 33 if (!item1 || !item2) { 34 return false 35 } 36 if (Array.isArray(item1)) { 37 item1 = rgb(item1) 38 } 39 if (Array.isArray(item2)) { 40 item2 = rgb(item2) 41 } 42 return item1.r === item2.r 43 && item1.g === item2.g 44 && item1.b === item2.b 45 } 46 47 /** 48 * Compares 2 coordinate values 49 * @param { [x, y] or {x: y:} } item1 50 * @param { [x, y] or {x: y:} } item2 51 */ 52 function coordsEqual(item1, item2) { 53 if (!item1 || !item2) { 54 return false 55 } 56 57 if (Array.isArray(item1)) { 58 item1 = xy(item1) 59 } 60 if (Array.isArray(item2)) { 61 item2 = xy(item2) 62 } 63 return item1.x === item2.x 64 && item1.y === item2.y 65 } 66 67 /** 68 * If I ever want to change the pixel format... 69 * I should really route all access to pixels through here. 70 * @param {number} x 71 * @param {number} y 72 * @param {[][][]} pixels 73 */ 74 function getPixel(x, y, pixels) { 75 let column = pixels[x] 76 if (!column) { 77 return 78 } 79 return column[y] 80 } 81 82 /** 83 * Finds contiguous regions of pixels of the same color. 84 * Returns an array of the x & y coordinates of every pixel in the region. 85 * Doesn't jump corners: only pixels that share a side are considered to 86 * join up. 87 * 88 * @param {number} x 89 * @param {number} y 90 * @param {[][]]} pixels 91 * @param {[]]} targetColor 92 * @param {[][]]} contiguousPixels 93 */ 94 function findContiguousPixels(x, y, pixels, targetColor = getPixel(x, y, pixels), contiguousPixels=[[x, y]]) { 95 let adjescent = [ 96 [x-1, y], 97 [x+1, y], 98 [x, y-1], 99 [x, y+1] 100 ] 101 102 adjescent.forEach((coord) => { 103 let px = xy(coord) 104 let pxCol = getPixel(px.x, px.y, pixels) 105 if (!pxCol) { 106 return 107 } 108 109 // add adjescents uniquely if they are the target color 110 let ix = contiguousPixels.findIndex((existingCoord) => coordsEqual(coord, existingCoord)) 111 if (ix !== -1) { 112 return 113 } 114 115 if (!colorEqual(pxCol, targetColor)) { 116 return 117 } 118 contiguousPixels.push(coord) 119 let morePixels = findContiguousPixels(px.x, px.y, pixels, targetColor, contiguousPixels) 120 contiguousPixels.concat(morePixels) 121 }) 122 123 return contiguousPixels 124 } 125 126 function rotatePixelsCounterClock(pixels) { 127 let rotateClock = (x, y, width, height) => { 128 return { 129 newx: -y + (width - 1), 130 newy : x 131 } 132 } 133 return transformPixels(pixels, rotateClock) 134 } 135 136 function rotatePixelsClock(pixels) { 137 let rotateClock = (x, y, width, height) => { 138 return { 139 newx: y, 140 newy : - x + (height - 1) 141 } 142 } 143 return transformPixels(pixels, rotateClock) 144 } 145 146 /** 147 * Apply an arbitrary transform to some pixels. 148 * Does not modify the original pixels, just returns the new ones 149 * 150 * @param {[][][]]} pixels 151 * @param {(x, y, width, height) => { newx: newy: }} transform 152 * @returns {[][][]} newPixels 153 */ 154 function transformPixels(pixels, transform) { 155 let width = pixels.length 156 let height = pixels[0].length 157 let newPixels = [] 158 for (let x = 0; x < width; x++) { 159 let column = [] 160 for (var y = 0; y < height; y++) { 161 column.push([0, 0, 0]) 162 } 163 newPixels.push(column) 164 } 165 166 for (let x = 0; x < width; x++) { 167 for (let y = 0; y < height; y++) { 168 let px = getPixel(x, y, pixels) 169 let {newx, newy} = transform(x, y, width, height) 170 newPixels[newx][newy] = px 171 } 172 } 173 return newPixels 174 } 175 176 /** 177 * Reads all frames from a GIF image, returns them as 3d arrays (x, y, color component) 178 * @param {string} base64GifData The GIF image encoded as base64 179 */ 180 function readGifFrames(base64GifData) { 181 let imageBytes = base64js.toByteArray(base64GifData) 182 let gifReader = new omggif.GifReader(imageBytes) 183 184 let { width, height } = gifReader 185 let numFrames = gifReader.numFrames() 186 let frames = [] 187 188 for (var i=0; i<numFrames; i++) { 189 let rawPixels = new Array(width * height * 4) 190 gifReader.decodeAndBlitFrameRGBA(i, rawPixels) 191 192 // Create the x, y array upfront 193 let pixels = new Array(width) 194 for (let y=0; y<height; y++) { 195 pixels[y] = new Array(height) 196 } 197 frames.push(pixels) 198 199 // Copy pixels to out array. The data provided is provided in rows 200 var ix = 0 201 for (let y=0; y<height; y++) { 202 for (let x=0; x<width; x++) { 203 let r = rawPixels[ix++] 204 let g = rawPixels[ix++] 205 let b = rawPixels[ix++] 206 ix++ // Ignore the alpha component 207 pixels[x][y] = [r, g, b] 208 } 209 } 210 } 211 212 return frames 213 } 214 215 export { 216 xy, 217 rgb, 218 colorEqual, 219 coordsEqual, 220 findContiguousPixels, 221 getPixel, 222 rotatePixelsClock, 223 rotatePixelsCounterClock, 224 readGifFrames 225 } 226