I am trying to make it to where a user can upload a file to the site and convert their file. Then download it back. I used JavaScripts Web audio api to do so. Now I just need to let the user download the file back. I create a blob but I am currently stuck.
ConverterSec2.jsx:
class ConverterSec2 extends Component {
render() {
return (
<div className="sec2">
<form>
<input type="file" id="audio-file" name="file" accept="audio/mpeg, audio/ogg, audio/*" />
<button type="Submit" id="convert_btn">Convert to 432Hz</button>
<script src="ihertz_website/src/pages/Converter/compressor.js"></script>
</form>
</div>
)
}
}
export default ConverterSec2
converting.js:
//render proccessed audio
offlineAudioCtx.startRendering().then(function(renderBuffer){
make_download(renderBuffer, offlineAudioCtx.length)
console.log("ERROR: PROCCESSING AUDIO")
})
//download Audio buffer as downloadable WAV file
function make_download(abuffer, total_samples) {
//get duration and sample rate
var duration = abuffer.duration,
rate = abuffer.sampleRate,
offset = 0;
var new_file = URL.createObjectURL(bufferToWave(abuffer, total_samples));
var download_link = document.getElementById("download_link");
download_link.href = new_file;
var name = generateFileName();
download_link.download = name;
}
//generate name of file
function generateFileName() {
var origin_name = fileInput.files[0].name;
var pos = origin_name.lastIndexOf('.');
var no_exit = origin_name.slice(0, pos);
return no_exit + ".wav";
}
//Convert an AudioBuffer to a Blob using WAVE representation
function bufferToWave(abuffer, len) {
var numOfChan = abuffer.numberOfChannels,
length = len * numOfChan * 2 + 44,
buffer = new ArrayBuffer(length),
view = new DataView(buffer),
channels = [], i, sample,
offset = 0,
pos = 0;
//write WAVE header
setUnit32(0x464664952);
setUnit32(length - 8);
setUint32(0x45564157); // "WAVE"
setUint32(0x20746d66); // "fmt " chunk
setUint32(16); // length = 16
setUint16(1); // PCM (uncompressed)
setUint16(numOfChan);
setUint32(abuffer.sampleRate);
setUint32(abuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit (hardcoded in this demo)
setUint32(0x61746164); // "data" - chunk
setUint32(length - pos - 4); // chunk length
// write interleaved data
for(i = 0; i < abuffer.numberOfChannels; i++)
channels.push(abuffer.getChannelData(i));
while(pos < length) {
for(i = 0; i < numOfChan; i++) { // interleave channels
sample = Math.max(-1, Math.min(1, channels[i][offset])); // clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767)|0; // scale to 16-bit signed int
view.setInt16(pos, sample, true); // write 16-bit sample
pos += 2;
}
offset++ // next source sample
}
//create Blob
return new Blob([buffer], {type: "audio/wav"});
function setUint16(data) {
view.setUint16(pos, data, true);
pos += 2;
}
function setUnit32(data) {
view.setUint32(pos, data, true);
pos += 4;
}
}
Related
I have already written a code to extract content from a csv file and add each chunk of text on each layer of illustrator. At the moment I could add content for fixed artboard. But I need to add separate art board while keeping the width 23mm. So, artboard height similar to content height of the particular layer. I'm getting an error "TypeError: text.parentArtboard is undefined"
Kindly help me to resolve the above issue.
var csv = '''clipName,Trans,fname
A1 ,test Text1,A1_ENG
A2 ,Pigment dyed fabric w.,A2_ENG
A3 ,UPF 50+ fabric. Only covered areas are protected. To maintain this level of protection the garment must be rinsed in fresh water after each use.,A3_ENG
A4 ,Light colours may become transparent when wet,A4_ENG'''
/* var csv_file = File.openDialog();
csv_file.open("r")
var csv = csv_file.read();
csv_file.close() */
var lines = csv.split("\n");
// MAIN -------------------------------------------------------------
// make character styles
var FONT1 = make_style("font1", "ArialMT", 5.5);
// process lines
for (var i=1; i<lines.length; i++) {
var data = get_data_from(lines[i]);
make_layer(data.name)
var text = make_text(data.contents);
apply_styles(text);
put_in_center(text);
// Create a new artboard
//artboard()
}
// END
// functions --------------------------------------------------------
function make_style(style_name, font_name, size) {
// try to add a new style
try { var style = app.activeDocument.characterStyles.add(style_name) }
// or pick a style with the same name if it exists already
catch(e) { var style = app.activeDocument.characterStyles.getByName(style_name) }
//var style = app.activeDocument.characterStyles.add(style_name);
style.characterAttributes.size = size;
style.characterAttributes.textFont = textFonts.getByName(font_name);
return style;
}
function addArtboard(text, artboardWidth ) {
// Get all layers in the text
var layers = text.layers;
// Set the artboard width to 23
var artboard = text.parentArtboard;
artboard.width = artboardWidth;
// Declare variable to keep track of content height
var contentHeight = 0;
// Loop through all layers
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
// Position the layer
layer.x = 0;
layer.y = contentHeight;
// Update content height
contentHeight += layer.height;
}
// Set the artboard height based on the content height
artboard.height = contentHeight;
}
function get_data_from(line) {
var arr = line.split(",");
var fname = arr[2]
var VL1 = arr[0];
var VL2 = arr[1];
//alert("Your message here...", fname);
//return {"name":Fname, "contents":[VL1, VL2, VL3, VL4,VL5, VL6, VL7, VL8, VL9]};
return {"name":fname, "contents":[VL2]};
}
//string.includes(substring)
function rearrangeText(text, artboardWidth) {
// Split the text into words
var words = text.split(" ");
var rearrangedText = "";
var currentLine = "";
// Loop through each word
for (var i = 0; i < words.length; i++) {
var word = words[i];
// If the current line + the next word would exceed the artboard width
if (currentLine.length + word.length > artboardWidth) {
// Add the current line to the rearranged text
rearrangedText += currentLine + "\n";
// Reset the current line
currentLine = "";
}
// Add the word to the current line
currentLine += word + " ";
}
// Add the last line to the rearranged text
rearrangedText += currentLine;
return rearrangedText;
}
function make_layer(layer_name) {
var new_layer = app.activeDocument.layers.add();
new_layer.name = layer_name;
}
function make_text(array) {
var text = app.activeDocument.textFrames.add();
text.contents = rearrangeText(array.join("\n"),23);
addArtboard(text.contents,23) //Try to add new artboar based on the text content size
return text;
}
function artboard() {
var layers = app.activeDocument.layers;
var textLayers = [];
for (var i = 0; i < layers.length; i++) {
if (layers[i].kind == LayerKind.TEXT) {
textLayers.push(layers[i]);
}
}
var width = 23; // width in mm
for (var i = 0; i < textLayers.length; i++) {
var textLayer = textLayers[i];
var textBounds = textLayer.bounds;
var artboardWidth = width * app.activeDocument.rulerUnits;
var artboardHeight = textBounds[3] - textBounds[1];
var artboard = app.activeDocument.artboards.add(textBounds);
artboard.width = artboardWidth;
artboard.height = artboardHeight;
textLayer.move(artboard);
}
}
function to_center(artboard, item){
var artboard_x = artboard.artboardRect[0] + artboard.artboardRect[2];
var artboard_y = artboard.artboardRect[1] + artboard.artboardRect[3];
var x = (artboard_x - item.width)/2;
var y = (artboard_y + item.height)/2;
item.position = [x, y];
}
function apply_styles(text) {
// not the best piece of code, I'm sure it can be done better
text.textRange.paragraphAttributes.justification = Justification.CENTER;
FONT1.applyTo(text.textRange);
}
function put_in_center(obj) {
var rect = app.activeDocument.artboards[0].artboardRect;
var page_w = rect[2] - rect[0];
var page_h = rect[1] - rect[3];
var shift_x = page_w/2 - obj.width/2;
var shift_y = -page_h/2 + obj.height/2;
obj.position = [rect[0] + shift_x, rect[1] + shift_y];
}
When I remove the function addArtboard(text, artboardWidth ) all content will be nicely rearrange based on the width of the artboard. But, artboard size doesn't change due to the code error.
Experts kindly help me with this.
Image of the content arrangement on illustrator layers are mention below
If understand the task correctly you can add this function into the code:
function distribute_texts_and_create_artboards() {
const mm = 2.83465; // mm/pt
var shift_x = 30 * mm; // horizontal shifs for texts
var width = 25 * mm; // artboard width
var doc = app.activeDocument;
var frames = doc.textFrames;
// move all the frames and create an artboard for the every text frame
for (var i=0; i<frames.length; i++) {
var frame = frames[i];
frame.translate(shift_x * i, 0); // move the frame horizontally
var frame_x1 = frame.geometricBounds[0];
var frame_y1 = frame.geometricBounds[1];
var frame_x2 = frame.geometricBounds[2];
var frame_y2 = frame.geometricBounds[3];
var artboard_x = frame_x1 - (width - frame.width)/2;
var artboard_y = frame_y1;
var artboard_w = frame_x2 + (width - frame.width)/2;
var artboard_h = frame.height;
var artboard_rect = [artboard_x, artboard_y, artboard_w, -artboard_h];
var new_ab = doc.artboards.add(artboard_rect); // create a new artboard
new_ab.name = frame.parent.name; // artboard name = layer name
// frame.position = [frame.position[0], frame.position[1]/2]; // to centred the frame vertically
}
doc.artboards[0].remove(); // delete the old first artboard
}
and call it at the end of the 'MAIN' section:
...
}
distribute_texts_and_create_artboards();
// END
...
and comment out the line:
// put_in_center(text);
It should give you the layout as follows:
Probably it makes sense to tweak the vertical position of the frames of the artboards a bit. But since you haven't provided an example of desired result I left it as is, for now. Let me know if you need it or uncomment the line:
frame.position = [frame.position[0], frame.position[1]/2];
And you don't need the functions addArtboard() and artboard() anymore in the code.
Update
Here is the function that saves all the text frames from layers as separate PDFs into the same folder as current AI file. File names are the same as layer names.
function save_layers_as_pdfs() {
var doc = app.activeDocument;
// pdf options
var options = new PDFSaveOptions();
options.compatibility = PDFCompatibility.ACROBAT5;
options.generateThumbnails = true;
options.preserveEditability = false;
options.preset = "[Smallest File Size]";
var mm = 2.83465; // mm/pt
var width = 25 * mm; // artboard width
var layers = doc.layers;
for (var i=0; i<layers.length; i++) {
var layer = layers[i];
layer.hasSelectedArtwork = true;
if (app.selection.length == 0) continue;
app.copy();
app.selection = null;
// create a new file
var file = File(doc.path + '/' + layer.name + '.pdf');
var new_doc = app.documents.add();
app.paste();
// change the artboard size
var frame = new_doc.textFrames[0];
var frame_x1 = frame.geometricBounds[0];
var frame_y1 = frame.geometricBounds[1];
var frame_x2 = frame.geometricBounds[2];
var frame_y2 = frame.geometricBounds[3];
var artboard_x = frame_x1 - (width - frame.width)/2;
var artboard_y = frame_y1;
var artboard_w = frame_x1 + frame.width + (width - frame.width)/2;
var artboard_h = frame_y1 - frame.height;
doc.artboards[0].artboardRect = [artboard_x, artboard_y, artboard_w, artboard_h];
// save a pdf and close document
new_doc.saveAs(file, options);
new_doc.close(SaveOptions.DONOTSAVECHANGES);
}
}
You can call this function at the end of the MAIN section.
The result:
And make sure your csv data is correct: the separators, quote marks, tabulations, etc.
The main problem is that after decoding file, I get PointCloud while a have to get Mesh. So common instruction from draco repository (https://github.com/google/draco/blob/master/javascript/npm/draco3d/draco_nodejs_example.js) does not suit me because I can't get num_faces from PointCloud. That's why I get 'mesh.num_faces is not a function'
I expect to get .drc compressed file from 3d model with .obj extension, but I get following error: "mesh.num_faces is not a function"
Here is my function for encoding. I took it from draco repository
const encoder = new this.encoderModule.Encoder();
const meshBuilder = new this.encoderModule.MeshBuilder();
const newMesh = new this.encoderModule.Mesh();
const numFaces = mesh.num_faces();
const numIndices = numFaces * 3;
const numPoints = mesh.num_points();
const indices = new Uint32Array(numIndices);
console.log('Number of faces ' + numFaces);
console.log('Number of vertices ' + numPoints);
// Add Faces to mesh
const ia = new this.decoderModule.DracoInt32Array();
for (let i = 0; i < numFaces; ++i) {
decoder.GetFaceFromMesh(mesh, i, ia);
const index = i * 3;
indices[index] = ia.GetValue(0);
indices[index + 1] = ia.GetValue(1);
indices[index + 2] = ia.GetValue(2);
}
this.decoderModule.destroy(ia);
meshBuilder.AddFacesToMesh(newMesh, numFaces, indices);
const attrs = { POSITION: 3, NORMAL: 3, COLOR: 3, TEX_COORD: 2 };
Object.keys(attrs).forEach((attr) => {
const stride = attrs[attr];
const numValues = numPoints * stride;
const decoderAttr = this.decoderModule[attr];
const encoderAttr = this.encoderModule[attr];
const attrId = decoder.GetAttributeId(mesh, decoderAttr);
if (attrId < 0) {
return;
}
console.log('Adding %s attribute', attr);
const attribute = decoder.GetAttribute(mesh, attrId);
const attributeData = new this.decoderModule.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(mesh, attribute, attributeData);
assert(numValues === attributeData.size(), 'Wrong attribute size.');
const attributeDataArray = new Float32Array(numValues);
for (let i = 0; i < numValues; ++i) {
attributeDataArray[i] = attributeData.GetValue(i);
}
this.decoderModule.destroy(attributeData);
meshBuilder.AddFloatAttributeToMesh(
newMesh,
encoderAttr,
numPoints,
stride,
attributeDataArray,
);
});
const encodedData = new this.encoderModule.DracoInt8Array();
// Set encoding options.
encoder.SetSpeedOptions(5, 5);
encoder.SetAttributeQuantization(this.encoderModule.POSITION, 10);
encoder.SetEncodingMethod(this.encoderModule.MESH_EDGEBREAKER_ENCODING);
// Encoding.
console.log('Encoding...');
const encodedLen = encoder.EncodeMeshToDracoBuffer(newMesh, encodedData);
this.encoderModule.destroy(newMesh);
if (encodedLen > 0) {
console.log('Encoded size is ' + encodedLen);
} else {
console.log('Error: Encoding failed.');
}
// Copy encoded data to buffer.
const outputBuffer = new ArrayBuffer(encodedLen);
const outputData = new Int8Array(outputBuffer);
for (let i = 0; i < encodedLen; ++i) {
outputData[i] = encodedData.GetValue(i);
}
this.encoderModule.destroy(encodedData);
this.encoderModule.destroy(encoder);
this.encoderModule.destroy(meshBuilder);
I have an issue with BiquadFilterNode (Web Audio API).
I made a simple audio program that repeats noise in every one bar.
The issue is when I connect a BiquadFilterNode to the noise, its volume keeps increasing gradually.
Could anyone kindly point out why the gradual volume increase is happening?
Many thanks!
Webpage running the program:
https://abirdwhale.github.io/javascript-web-audio-api-sketches/sequencers/test-biquadfilter-issue/
GitHub repository to recreate the issue:
https://github.com/abirdwhale/javascript-web-audio-api-sketches/tree/main/sequencers/test-biquadfilter-issue
HTML:
<!-- <!DOCTYPE html> -->
<html>
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<button id="play-button">Play/Pause</button>
<script type="module" src="js/app.js"></script>
</body>
</html>
JavaScript:
"use strict";
// for cross browser
const AudioContext = window.AudioContext || window.webkitaudioCtx;
const audioCtx = new AudioContext();
let futureTickTime = audioCtx.currentTime;
let counter = 1;
let tempo = 120;
let secondsPerBeat = 60 / tempo;
let counterTimeValue = (secondsPerBeat / 4); // 16th note
// Noise volume
let noiseVolume = audioCtx.createGain();
noiseVolume.gain.value = 0.001; //Noise volume before send to FX
// Noise parameters
let noiseDuration = 1.; //Duration of Noise
let bandHz = 100;
function playNoise(time, playing) {
if (playing) {
const bufferSize = audioCtx.sampleRate * noiseDuration; // set the time of the note
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); // create an empty buffer
const data = buffer.getChannelData(0); // get data
// fill the buffer with noise
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
// create a buffer source for our created data
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const bandpass = audioCtx.createBiquadFilter();
bandpass.type = 'bandpass';
bandpass.frequency.value = bandHz;
// connect our graph
noise.connect(noiseVolume);
// 1. without a bandpass filter
// noiseVolume.connect(audioCtx.destination);
// 2. with a bandpass filter
noiseVolume.connect(bandpass).connect(audioCtx.destination);
if (counter === 1) {
noise.start(time);
// noise.stop(time + noiseDuration);
}
}
}
const lookahead = 25.0; // How frequently to call scheduling function (in milliseconds)
const scheduleAheadTime = 0.1; // How far ahead to schedule audio (sec)
function playTick() {
console.log("This 16th note is: " + counter);
console.log("16th is: " + counterTimeValue);
console.log("futureTickTime: " + futureTickTime);
console.log("Web Audio Time: " + audioCtx.currentTime);
counter += 1;
futureTickTime += counterTimeValue;
console.log("futureTickTime: " + futureTickTime);
if (counter > 16) {
counter = 1;
}
}
function scheduler() {
if (futureTickTime < audioCtx.currentTime + scheduleAheadTime) {
playNoise(futureTickTime, true);
playTick();
}
window.setTimeout(scheduler, lookahead);
}
scheduler();
document.getElementById("play-button").addEventListener("click", function () {
if (audioCtx.state !== "running") {
console.log("it's not running well");
audioCtx.resume();
} else {
console.log("it's running");
audioCtx.suspend();
console.log(audioCtx.state);
}
});
OK, taking BiquadFilterNode setup out of the playNoise function solved the issue. I don't understand why placing the setup inside the function was causing the issue, though.
Fixed JavaScript:
"use strict";
// for cross browser
const AudioContext = window.AudioContext || window.webkitaudioCtx;
const audioCtx = new AudioContext();
let futureTickTime = audioCtx.currentTime;
let counter = 1;
let tempo = 120;
let secondsPerBeat = 60 / tempo;
let counterTimeValue = (secondsPerBeat / 4); // 16th note
// Noise volume
let noiseVolume = audioCtx.createGain();
noiseVolume.gain.value = 0.1; //Noise volume before send to FX
// Noise parameters
let noiseDuration = 1.; //Duration of Noise
let bandHz = 100;
// Biquad filter setup
const bandpass = audioCtx.createBiquadFilter();
bandpass.type = 'bandpass';
bandpass.frequency.value = bandHz;
function playNoise(time, playing) {
if (playing) {
const bufferSize = audioCtx.sampleRate * noiseDuration; // set the time of the note
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate); // create an empty buffer
const data = buffer.getChannelData(0); // get data
// fill the buffer with noise
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
// create a buffer source for our created data
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
// connect our graph
noise.connect(noiseVolume);
// 1. without a bandpass filter
// noiseVolume.connect(audioCtx.destination);
// 2. with a bandpass filter
noiseVolume.connect(bandpass).connect(audioCtx.destination);
if (counter === 1) {
// bandpass.gain.setValueAtTime(-40, time);
noise.start(time);
// noise.stop(time + noiseDuration);
}
}
}
const lookahead = 25.0; // How frequently to call scheduling function (in milliseconds)
const scheduleAheadTime = 0.1; // How far ahead to schedule audio (sec)
function playTick() {
console.log("This 16th note is: " + counter);
console.log("16th is: " + counterTimeValue);
console.log("futureTickTime: " + futureTickTime);
console.log("Web Audio Time: " + audioCtx.currentTime);
counter += 1;
futureTickTime += counterTimeValue;
console.log("futureTickTime: " + futureTickTime);
if (counter > 16) {
counter = 1;
}
}
function scheduler() {
if (futureTickTime < audioCtx.currentTime + scheduleAheadTime) {
playNoise(futureTickTime, true);
playTick();
}
window.setTimeout(scheduler, lookahead);
}
scheduler();
document.getElementById("play-button").addEventListener("click", function () {
if (audioCtx.state !== "running") {
console.log("it's not running well");
audioCtx.resume();
} else {
console.log("it's running");
audioCtx.suspend();
console.log(audioCtx.state);
}
});
I am using https://github.com/jakubfiala/soundtouch-js soundtouch library to do some pitch shifting on audio files. the lib works fine with pitch shifting but I don't know how to control the audio file's channels to switch them on/off.
I am following this pen https://codepen.io/ezzatly/pen/LLqOgJ?editors=1010
var source = {
extract: function (target, numFrames, position) {
$("#current-time").html(minsSecs(position / (context.sampleRate)));
console.log('width: ', 100 * position / (bufferDuration * context.sampleRate));
$("#progress").width(100 * position / (bufferDuration * context.sampleRate) + "%");
if (Math.round(100 * position / (bufferDuration * context.sampleRate)) == 100 && is_playing) {
//stop recorder
recorder && recorder.stop();
__log('Recording complete.');
// create WAV download link using audio data blob
createDownloadLink();
if (typeof recorder != "undefined") {
recorder.clear();
}
is_playing = false;
}
var l = buffer.getChannelData(0);
if (buffer.numberofChannels > 1) {
var r = buffer.getChannelData(1);
} else {
var r = buffer.getChannelData(0);
}
for (var i = 0; i < numFrames; i++) {
target[i * 2] = l[i + position];
target[i * 2 + 1] = r[i + position];
}
return Math.min(numFrames, l.length - position);
}
};
node.onaudioprocess = function (e) {
if (buffer.getChannelData) {
pos += BUFFER_SIZE / context.sampleRate;
console.log('position: ', pos);
var l = e.outputBuffer.getChannelData(0);
var r = e.outputBuffer.getChannelData(1);
var framesExtracted = f.extract(samples, BUFFER_SIZE);
if (framesExtracted == 0) {
node.disconnect();
}
for (var i = 0; i < framesExtracted; i++) {
l[i] = samples[i * 2];
r[i] = samples[i * 2 + 1];
}
leftchannel.push(new Float32Array(l));
rightchannel.push(new Float32Array(r));
recordingLength += BUFFER_SIZE;
}
};
any help?
First, I have a slightly more modernized version of SoundTouchJs in my GitHub. Might make it slightly easier to hack around.
Second, you probably want to attach a ChannelSplitterNode to your connected audio context. I haven't played with this, but I would think that's what you would need to independently control the gain on each channel. Again, I'm guessing, but hopefully that'll push you in the right direction.
I am trying to desteghide an image to reveal the secret message.
I have got it working, but only by browsing the image which contains the message.
If you take a look at the screenshot:
To make this work I need to save the image (the arsenal badge). Then use the browse to upload the image again to make this work. I want to skip the browse stage and make automatically by grabbing the image straight from the div.
Any ideas?
here is my code (its quite messy sorry)
window.onload = function() {
// add action to the file input
var input = document.getElementById('file');
input.addEventListener('change', importImage);
};
var maxMessageSize = 1000;
$("#desteg").click(function()
{
decode();
});
var importImage = function(e) {
var reader = new FileReader();
reader.onload = function(event) {
// set the preview
document.getElementById('preview').style.display = 'block';
document.getElementById('preview').src = event.target.result;
// wipe all the fields clean
document.getElementById('pass').value = '';
document.getElementById('ContentArea').innerHTML = '';
// read the data into the canvas element
var img = new Image();
img.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
decode();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
};
// encode the image and save it
// decode the image and display the contents if there is anything
var decode = function() {
var password = document.getElementById('pass').value;
var passwordFail = 'Password is incorrect or there is nothing here.';
// decode the message with the supplied password
var ctx = document.getElementById('canvas').getContext('2d');
var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
var message = decodeMessage(imgData.data, sjcl.hash.sha256.hash(password));
// try to parse the JSON
var obj = null;
try {
obj = JSON.parse(message);
} catch (e) {
// display the "choose" view
if (password.length > 0) {
alert(passwordFail);
}
}
// display the "reveal" view
if (obj) {
// decrypt if necessary
if (obj.ct) {
try {
obj.text = sjcl.decrypt(password, message);
} catch (e) {
alert(passwordFail);
}
}
// escape special characters
var escChars = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
'\'': ''',
'/': '/',
'\n': '<br/>'
};
var escHtml = function(string) {
return String(string).replace(/[&<>"'\/\n]/g, function (c) {
return escChars[c];
});
};
document.getElementById('ContentArea').innerHTML = escHtml(obj.text);
}
};
// returns a 1 or 0 for the bit in 'location'
var getBit = function(number, location) {
return ((number >> location) & 1);
};
// sets the bit in 'location' to 'bit' (either a 1 or 0)
var setBit = function(number, location, bit) {
return (number & ~(1 << location)) | (bit << location);
};
// returns an array of 1s and 0s for a 2-byte number
var getBitsFromNumber = function(number) {
var bits = [];
for (var i = 0; i < 16; i++) {
bits.push(getBit(number, i));
}
return bits;
};
// returns the next 2-byte number
var getNumberFromBits = function(bytes, history, hash) {
var number = 0, pos = 0;
while (pos < 16) {
var loc = getNextLocation(history, hash, bytes.length);
var bit = getBit(bytes[loc], 0);
number = setBit(number, pos, bit);
pos++;
}
return number;
};
// returns an array of 1s and 0s for the string 'message'
var getMessageBits = function(message) {
var messageBits = [];
for (var i = 0; i < message.length; i++) {
var code = message.charCodeAt(i);
messageBits = messageBits.concat(getBitsFromNumber(code));
}
return messageBits;
};
// gets the next location to store a bit
var getNextLocation = function(history, hash, total) {
var pos = history.length;
var loc = Math.abs(hash[pos % hash.length] * (pos + 1)) % total;
while (true) {
if (loc >= total) {
loc = 0;
} else if (history.indexOf(loc) >= 0) {
loc++;
} else if ((loc + 1) % 4 === 0) {
loc++;
} else {
history.push(loc);
return loc;
}
}
};
// encodes the supplied 'message' into the CanvasPixelArray 'colors'
// returns the message encoded in the CanvasPixelArray 'colors'
var decodeMessage = function(colors, hash) {
// this will store the color values we've already read from
var history = [];
// get the message size
var messageSize = getNumberFromBits(colors, history, hash);
// exit early if the message is too big for the image
if ((messageSize + 1) * 16 > colors.length * 0.75) {
return '';
}
// exit early if the message is above an artificial limit
if (messageSize === 0 || messageSize > maxMessageSize) {
return '';
}
// put each character into an array
var message = [];
for (var i = 0; i < messageSize; i++) {
var code = getNumberFromBits(colors, history, hash);
message.push(String.fromCharCode(code));
}
// the characters should parse into valid JSON
return message.join('');
};