qrcode-generator-ts
Version:
Typescript QR Code generator based on https://github.com/kazuhikoarase/qrcode-generator
266 lines (208 loc) • 6.07 kB
text/typescript
import { Base64 } from '../io/Base64';
import { OutputStream } from '../io/OutputStream';
import { ByteArrayOutputStream } from '../io/ByteArrayOutputStream';
'use strict';
/**
* GIF Image (B/W)
* @author Kazuhiko Arase
*/
export class GIFImage {
private width: number;
private height: number;
private data: number[];
constructor(width: number, height: number) {
this.width = width;
this.height = height;
var size = width * height;
this.data = [];
for (var i = 0; i < size; i += 1) {
this.data.push(0);
}
}
public setPixel(x: number, y: number, pixel: number): void {
if (x < 0 || this.width <= x) throw '!' + x;
if (y < 0 || this.height <= y) throw '!' + y;
this.data[y * this.width + x] = pixel;
}
public getPixel(x: number, y: number): number {
if (x < 0 || this.width <= x) throw '!' + x;
if (y < 0 || this.height <= y) throw '!' + y;
return this.data[y * this.width + x];
}
public write(out: OutputStream): void {
//---------------------------------
// GIF Signature
out.writeByte('G'.charCodeAt(0));
out.writeByte('I'.charCodeAt(0));
out.writeByte('F'.charCodeAt(0));
out.writeByte('8'.charCodeAt(0));
out.writeByte('7'.charCodeAt(0));
out.writeByte('a'.charCodeAt(0));
//---------------------------------
// Screen Descriptor
this.writeWord(out, this.width);
this.writeWord(out, this.height);
out.writeByte(0x80); // 2bit
out.writeByte(0);
out.writeByte(0);
//---------------------------------
// Global Color Map
// black
out.writeByte(0x00);
out.writeByte(0x00);
out.writeByte(0x00);
// white
out.writeByte(0xff);
out.writeByte(0xff);
out.writeByte(0xff);
//---------------------------------
// Image Descriptor
out.writeByte(','.charCodeAt(0));
this.writeWord(out, 0);
this.writeWord(out, 0);
this.writeWord(out, this.width);
this.writeWord(out, this.height);
out.writeByte(0);
//---------------------------------
// Local Color Map
//---------------------------------
// Raster Data
var lzwMinCodeSize = 2;
var raster = this.getLZWRaster(lzwMinCodeSize);
out.writeByte(lzwMinCodeSize);
var offset: number = 0;
while (raster.length - offset > 255) {
out.writeByte(255);
this.writeBytes(out, raster, offset, 255);
offset += 255;
}
out.writeByte(raster.length - offset);
this.writeBytes(out, raster, offset, raster.length - offset);
out.writeByte(0x00);
//---------------------------------
// GIF Terminator
out.writeByte(';'.charCodeAt(0));
}
private getLZWRaster(lzwMinCodeSize: number): number[] {
var clearCode = 1 << lzwMinCodeSize;
var endCode = (1 << lzwMinCodeSize) + 1;
var bitLength = lzwMinCodeSize + 1;
// Setup LZWTable
var table = new LZWTable();
for (var i = 0; i < clearCode; i += 1) {
table.add(String.fromCharCode(i));
}
table.add(String.fromCharCode(clearCode));
table.add(String.fromCharCode(endCode));
var byteOut = new ByteArrayOutputStream();
var bitOut = new BitOutputStream(byteOut);
try {
// clear code
bitOut.write(clearCode, bitLength);
var dataIndex = 0;
var s = String.fromCharCode(this.data[dataIndex]);
dataIndex += 1;
while (dataIndex < this.data.length) {
var c = String.fromCharCode(this.data[dataIndex]);
dataIndex += 1;
if (table.contains(s + c)) {
s = s + c;
} else {
bitOut.write(table.indexOf(s), bitLength);
if (table.getSize() < 0xfff) {
if (table.getSize() == (1 << bitLength)) {
bitLength += 1;
}
table.add(s + c);
}
s = c;
}
}
bitOut.write(table.indexOf(s), bitLength);
// end code
bitOut.write(endCode, bitLength);
} finally {
bitOut.close();
}
return byteOut.toByteArray();
}
private writeWord(out: OutputStream, i: number) {
out.writeByte(i & 0xff);
out.writeByte((i >>> 8) & 0xff);
}
private writeBytes(
out: OutputStream,
bytes: number[], off: number, len: number
) {
for (var i = 0; i < len; i += 1) {
out.writeByte(bytes[i + off]);
}
}
public toDataURL(): string {
var bout = new ByteArrayOutputStream();
this.write(bout);
bout.close();
var s = '';
var bytes = Base64.encode(bout.toByteArray());
for (var i = 0; i < bytes.length; i += 1) {
s += String.fromCharCode(bytes[i]);
}
return 'data:image/gif;base64,' + s;
}
}
class LZWTable {
private map: { [key: string]: number; } = {};
private size: number = 0;
constructor() {
}
public add(key: string): void {
if (this.contains(key)) {
throw 'dup key:' + key;
}
this.map[key] = this.size;
this.size += 1;
}
public getSize(): number {
return this.size;
}
public indexOf(key: string): number {
return this.map[key];
}
public contains(key: string): boolean {
return typeof this.map[key] != 'undefined';
}
}
class BitOutputStream {
private out: OutputStream;
private bitLength: number;
private bitBuffer: number;
constructor(out: OutputStream) {
this.out = out;
this.bitLength = 0;
}
public write(data: number, length: number): void {
if ((data >>> length) != 0) {
throw 'length over';
}
while (this.bitLength + length >= 8) {
this.out.writeByte(0xff &
((data << this.bitLength) | this.bitBuffer));
length -= (8 - this.bitLength);
data >>>= (8 - this.bitLength);
this.bitBuffer = 0;
this.bitLength = 0;
}
this.bitBuffer = (data << this.bitLength) | this.bitBuffer;
this.bitLength = this.bitLength + length;
}
public flush(): void {
if (this.bitLength > 0) {
this.out.writeByte(this.bitBuffer);
}
this.out.flush();
}
public close(): void {
this.flush();
this.out.close();
}
}