UNPKG

agentscript

Version:

AgentScript Model in Model/View architecture

146 lines (126 loc) 4.55 kB
import * as util from './utils.js' // /** @namespace */ /** @module */ /** * @param {Image|Imageable} img img: image, canvas, context2d, url, data url * @returns A Canvas.context object */ async function toContext(img) { const type = util.typeOf(img) switch (type) { // image url or html element case 'string': // Note: drop thru to image img = await util.imagePromise(img) case 'htmlimageelement': return util.imageToCtx(img) // canvas case 'htmlcanvaselement': case 'offscreencanvas': return img.getContext('2d') // image 2D context case 'canvasrenderingcontext2d': return img default: throw Error('toContext: bad img type: ' + type) } } // msg can be string or Uint8Array /** * @param {*} msg A string, charCode (number), Uint8Array or Uint8ClampedArray * @returns A Uint8Array or Uint8ClampedArray */ function toUint8Array(msg) { const type = util.typeOf(msg) switch (type) { case 'number': msg = String.fromCharCode(msg) // drop thru to 'string' case 'string': return new TextEncoder().encode(msg) case 'uint8array': case 'uint8clampedarray': return msg default: throw Error('toUint8Array: bad msg type: ' + type) } } // Convert a char to an array of three RGB low order pixel values /** * * @param {} char An 8 bit char * @returns Array of 3 parts of char using the bits tempate (3, 2, 3 bits) */ function charToBits(char) { // return [char >> 5, char >> 3 & 0b11111100, char & 0b11111000] return [ char >> bits[0].shift, (char >> bits[1].shift) & bits[1].msgMask, char & bits[2].msgMask, ] } const bits = [ { shift: 5, msgMask: 0b00000111, dataMask: 0b11111000 }, // bits: 3 { shift: 3, msgMask: 0b00000011, dataMask: 0b11111100 }, // bits: 2 { shift: 0, msgMask: 0b00000111, dataMask: 0b11111000 }, // bits: 3 ] function checkSize(msg, width, height) { const imgSize = width * height // 1 px = 1 byte if (imgSize < msg.length) throw Error(`encode: image size < msg.length: ${imgSize} ${msg.length}`) } // Return the character length of the message within the image's imgData export function stegMsgSize(imgData) { for (let i = 3; i < imgData.length; i = i + 4) { if (imgData[i] === 254) return (i - 3) / 4 } throw Error( `decode: no message terminator in image data, length = ${imgData.length}` ) } // ============== encode/decode ============== // img can be png/jpeg image, url, dataurl, context2d, canvas // msg is a string or a Uint8 array export async function encode(img, msg) { const ctx = await toContext(img) const { width, height } = ctx.canvas checkSize(msg, width, height) const msgArray = toUint8Array(msg) console.log('msg buffer', msgArray) const imageData = ctx.getImageData(0, 0, width, height) const data = imageData.data console.log('imgageData.data', data) let ix msgArray.forEach((char, i) => { const [ch0, ch1, ch2] = charToBits(char) ix = i * 4 data[ix] = (data[ix++] & bits[0].dataMask) + ch0 data[ix] = (data[ix++] & bits[1].dataMask) + ch1 data[ix] = (data[ix++] & bits[2].dataMask) + ch2 data[ix] = 255 }) data[ix + 4] = 254 // use alpha to terminate message console.log('encoded imgageData.data', data) ctx.putImageData(imageData, 0, 0) console.log('msg length', msg.length) console.log('encode: embedded msg size', stegMsgSize(data)) return ctx } // img can be png/jpeg image, url, dataurl, context2d, canvas // returnU8: return a UintTypedArray; default return string export async function decode(img, returnU8 = false) { const ctx = await toContext(img) const { width, height } = ctx.canvas const data = ctx.getImageData(0, 0, width, height).data const msgSize = stegMsgSize(data) console.log('decode: embedded msg size', msgSize) const msgArray = new Uint8Array(msgSize) msgArray.forEach((char, i) => { let ix = i * 4 const ch0 = (bits[0].msgMask & data[ix++]) << bits[0].shift const ch1 = (bits[1].msgMask & data[ix++]) << bits[1].shift const ch2 = (bits[2].msgMask & data[ix++]) << bits[2].shift msgArray[i] = ch0 + ch1 + ch2 }) console.log('decode msgArray', msgArray) if (returnU8) return msgArray return new TextDecoder().decode(msgArray) }