-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
346 lines (310 loc) · 12.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
import * as BinaryField from "./binaryField.js";
import Vector from "./vector.js";
import Matrix from "./matrix.js";
import { B, B11 } from "./constants.js";
import GolayCode from "./golayCode.js";
import Channel from "./channel.js";
import ImgUtils from "./imgUtils.js"
let globalState = {
channel: new Channel(Number($("#errProbabilityField").val())),
code: new GolayCode()
}
initUI();
/**
* Initializes all UI components
*/
function initUI() {
initErrProbUI();
initVecEncUI();
initTextEncUI();
initImgEncUI();
setInputFilter($('#originalVectorField')[0], val => /^[0-1]*$/.test(val));
setInputFilter($('#transmittedVecField')[0], val => /^[0-1]*$/.test(val));
/**
* Initializes error probability slider UI part
*/
function initErrProbUI() {
$("#errProbabilitySlider").on("input", function () {
$("#errProbabilityField").val($(this).val());
globalState.channel.setErrProbability($(this).val());
});
$("#errProbabilityField").on("input", function () {
let val = $(this).val();
if (!isNaN(val) && val != "0." && val >= 0 && val <= 1) {
$("#errProbabilitySlider").val(val);
globalState.channel.setErrProbability(val);
}
});
}
/**
* Initializes single vector encoding UI part.
*/
function initVecEncUI() {
let state = {
encoded: undefined,
transmitted: undefined,
previousTransmittedVec: undefined
}
$("#modifyVecCheck").on("input", function () {
if ($(this).prop("checked")) {
$(".readonly-transmitted-vec").css("display", "none");
$(".transmitted-vec").css("display", "block");
state.previousTransmittedVec = state.transmitted;
} else {
$(".readonly-transmitted-vec").css("display", "block");
$(".transmitted-vec").css("display", "none");
if (state.transmitted && (state.transmitted.length() != 23 || state.transmitted.getVec().some(bit => bit != 0 && bit != 1))) {
setStateTransmitted(state.previousTransmittedVec || new Vector([]));
}
}
});
$('#originalVectorField').on("input", function () {
let vecStr = $(this).val()
if (vecStr.length === 12) {
let encoded = globalState.code.encode(new Vector(Array.from(vecStr, Number)));
state.encoded = encoded;
$('#encodedVecField').val(encoded.getVec().join(''));
}
});
$("#sendEncodedVectorBtn").on("click", function () {
if (!state.encoded) { return; }
let encoded = state.encoded,
transmitted = globalState.channel.transmitVector(encoded),
diff = transmitted.add(encoded);
setStateTransmitted(transmitted);
let addedChars = 0;
for (let i = 0; i < diff.length() + addedChars; i++) {
if (diff.getVec()[i] == 1) {
$("#transmittedVecFieldReadonly").children().eq(i).addClass('red-color');
}
}
});
$("#transmittedVecField").on("input", function () {
let updatedTransmitted = new Vector(Array.from($(this).val(), Number));
setStateTransmitted(updatedTransmitted);
});
$("#decodeVecButton").on("click", function () {
let decoded = globalState.code.decode(state.transmitted);
$("#decodedVecField").val(decoded.getVec().join(''));
})
/**
* Updates state value of transmitted vector including UI components.
* @param {Vector} transmitted
*/
function setStateTransmitted(transmitted) {
state.transmitted = transmitted;
let transmittedStr = transmitted.getVec().join('');
$("#transmittedVecField").val(transmittedStr);
$("#transmittedVecFieldReadonly").html("");
for (let bit of transmittedStr) {
$("#transmittedVecFieldReadonly").append("<span>" + bit + "</span>");
}
}
}
/**
* Initializes text encoding UI part.
*/
function initTextEncUI() {
$("#sendTextButton").on("click", function () {
let text = $("#textInputArea").val(),
encoder = new TextEncoder(),
decoder = new TextDecoder(),
bytes = encoder.encode(text),
bitVec = Vector.fromBytes(bytes),
leftoverBits = (12 - bitVec.length() % 12) % 12,
chunkCount = Math.floor(bitVec.length() / 12);
/** @type {Vector[]} */
let splitVectors = new Array(chunkCount),
i;
for (i = 0; i < chunkCount; i++) {
splitVectors[i] = new Vector(bitVec.getVec().slice(12 * i, 12 * i + 12));
}
if (leftoverBits != 0) {
splitVectors.push(new Vector(bitVec.getVec().slice(12 * i, 12 * i + 12 - leftoverBits)));
splitVectors[splitVectors.length - 1].getVec().push(...new Array(leftoverBits).fill(0));
}
let decodedUnchangedBytes = processPartitionedBits(
splitVectors,
leftoverBits,
vec => globalState.code.decodeNoCorrection(vec)
);
let correctedBytes = processPartitionedBits(
splitVectors,
leftoverBits,
vec => globalState.code.decode(vec)
)
$("#textUnchangedArea").val(decoder.decode(new Uint8Array(decodedUnchangedBytes)));
$("#textCorrectedArea").val(decoder.decode(new Uint8Array(correctedBytes)));
});
}
/**
* Initializes image encoding UI part.
*/
function initImgEncUI() {
$('#imgFile').on("input", function (evt) {
let file = evt.target.files[0];
let fr = new FileReader();
fr.onload = function () {
$('#uploadedImg')[0].src = fr.result;
$('#uploadedImg').css('border', 'none');
}
fr.readAsDataURL(file);
});
$("#sendPictureBtn").on("click", function () {
toDataURL($('#uploadedImg')[0].src, function (dataURL) {
let [dataURLHead, rawBase64] = dataURL.split(','),
{header: imgHeader, data: bitVec} = ImgUtils.splitHeader(dataURLHead, rawBase64),
leftoverBits = (12 - bitVec.length() % 12) % 12,
chunkCount = Math.floor(bitVec.length() / 12);
/** @type {Vector[]} */
let splitVectors = new Array(chunkCount),
i;
for (i = 0; i < chunkCount; i++) {
splitVectors[i] = new Vector(bitVec.getVec().slice(12 * i, 12 * i + 12));
}
if (leftoverBits != 0) {
splitVectors.push(new Vector(bitVec.getVec().slice(12 * i, 12 * i + 12 - leftoverBits)));
splitVectors[splitVectors.length - 1].getVec().push(...new Array(leftoverBits).fill(0));
}
let decodedUnchangedBytes = processPartitionedBits(
splitVectors,
leftoverBits,
vec => globalState.code.decodeNoCorrection(vec)
);
$("#unchangedImg").attr("src", dataURLHead + ','
+ btoa(imgHeader + decodedUnchangedBytes.reduce(
(acum, byte) => acum + String.fromCharCode(byte), ""))
);
$("#unchangedImg").css('border', 'none');
let correctedBytes = processPartitionedBits(
splitVectors,
leftoverBits,
vec => globalState.code.decode(vec)
)
$("#correctedImg").attr("src", dataURLHead + ','
+ btoa(imgHeader + correctedBytes.reduce(
(acum, byte) => acum + String.fromCharCode(byte), ""))
);
$("#correctedImg").css('border', 'none');
});
});
}
}
/**
* Processes partitioned vector of bits, performs encoding, transmitting through
* channel and decoding of each vector in bits array. Finally, convert results back to bytes.
* @param {Vector[]} bits
* Partitioned bit vector to be processed.
* @param {number} leftoverBits
* number of leftover bits that should be discarded in the end.
* @param {function(Vector): Vector} decoder
* A decoder function used internally to decode each vector of bits.
* @returns {number[]}
* Array of bytes converted from processed bits.
*/
function processPartitionedBits(bits, leftoverBits, decoder) {
let bitBuffer = [],
result = [];
for (let vec of bits) {
let encodedVec = globalState.code.encode(vec),
transmittedVec = globalState.channel.transmitVector(encodedVec),
decodedVec = decoder(transmittedVec);
let bytesCount = Math.floor((decodedVec.length() + bitBuffer.length) / 8);
while (decodedVec.length() > 0) {
bitBuffer.push(decodedVec.getVec().shift())
}
for (let i = 0; i < bytesCount; i++) {
let byte = 0;
for (let j = 0; j < 8; j++) {
byte = byte | (bitBuffer.shift() << 7 - j);
}
result.push(byte);
}
}
if (leftoverBits >= 8) result.pop();
return result;
}
/**
* Adds filters for textbox specified as a predicate callback inputFilter
* @param {HTMLElement} textbox
* HTML input element to have filters applied.
* @param {function(string): boolean} inputFilter
* Input filter predicate that is called with textbox value string for validation.
*/
function setInputFilter(textbox, inputFilter) {
["input", "keydown", "keyup", "mousedown", "mouseup", "select", "contextmenu", "drop"].forEach(function (event) {
textbox.addEventListener(event, function () {
if (inputFilter(this.value)) {
this.oldValue = this.value;
this.oldSelectionStart = this.selectionStart;
this.oldSelectionEnd = this.selectionEnd;
} else if (this.hasOwnProperty("oldValue")) {
this.value = this.oldValue;
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
} else {
this.value = "";
}
});
});
}
/**
* Picks image from specified url and reads it as ArrayBuffer.
* Result is transferred as parameter to callback function.
* @param {string} url
* @param {function(string|ArrayBuffer)} callback
*/
function toDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'blob';
xhr.onload = function () {
var fr = new FileReader();
fr.onload = function () {
callback(this.result);
};
fr.readAsDataURL(xhr.response); // async call
};
xhr.send();
}
/**
* Function to generate random vectors to test encoding and decoding algorithm.
* Encoded vector always has 3 bits distorted before decoding.
* @param {number} times
* number of times to generate.
*/
function testRandomVectors(times) {
let code = new GolayCode();
for (let i = 0; i < times; i++) {
let original = Array.from({ length: 12 }, () => Math.floor(Math.random() * 2)),
arr = code.encode(new Vector(original)).getVec(),
n = 3,
len = arr.length,
taken = new Array(len);
while (n--) {
let x = Math.floor(Math.random() * len);
let index = x in taken ? taken[x] : x
arr[index] = arr[index] == 0 ? 1 : 0;
taken[x] = --len in taken ? taken[len] : len;
}
let decoded = code.decode(new Vector(arr)).getVec();
if (JSON.stringify(original) !== JSON.stringify(decoded)) {
throw "Test case failed";
}
}
}
function testEfficienty(errProb) {
let code = new GolayCode();
let channel = new Channel(errProb);
let times = 10000;
let countCorrect = 0;
for (let i = 0; i < times; i++) {
let original = Array.from({ length: 12 }, () => Math.floor(Math.random() * 2)),
arr = code.encode(new Vector(original)),
sent = channel.transmitVector(arr),
decoded = code.decode(sent).getVec();
if (JSON.stringify(original) === JSON.stringify(decoded)) {
countCorrect++;
}
}
return countCorrect / times;
}