@sl-utils/map 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/package.json +32 -0
- package/src/canvas/index.ts +4 -0
- package/src/canvas/slu-canvas-gif.ts +600 -0
- package/src/canvas/slu-canvas-img.ts +54 -0
- package/src/canvas/slu-canvas-text.ts +179 -0
- package/src/canvas/slu-canvas.ts +188 -0
- package/src/index.ts +0 -0
- package/src/map/canvas-draw.ts +250 -0
- package/src/map/canvas-event.ts +283 -0
- package/src/map/canvas-layer.ts +191 -0
- package/src/utils/slu-map.ts +427 -0
- package/src/utils/txt.ts +251 -0
- package/tsconfig.json +23 -0
- package/types/amap.d.ts +7542 -0
- package/types/core.d.ts +894 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**canvas绘制gif工具类 */
|
|
2
|
+
export class SLUCanvasGif {
|
|
3
|
+
constructor() { }
|
|
4
|
+
/** 用来拿 imagedata 的工具人*/
|
|
5
|
+
private canvasTool: HTMLCanvasElement = document.createElement("canvas");
|
|
6
|
+
/** 工具人的 getContext('2d')不能初始化,否则可能出现空白帧*/
|
|
7
|
+
private ctx: CanvasRenderingContext2D;
|
|
8
|
+
private gifInfo: GifInfo;
|
|
9
|
+
private LAST_DISPOSA_METHOD: number | null = null;
|
|
10
|
+
/**当前帧的下标*/
|
|
11
|
+
private CURRENT_FRAME_INDEX = -1;
|
|
12
|
+
private TRANSPARENCY: any = null;
|
|
13
|
+
private CTX: CanvasRenderingContext2D;
|
|
14
|
+
/**缓存的数据 */
|
|
15
|
+
private gifCache: { [url: string]: GifCache } = {};
|
|
16
|
+
/**存放动画id用于停止之前绘制的动画 */
|
|
17
|
+
private aniIds: { [id: string]: number | null } = {};
|
|
18
|
+
private opts: CanvasGifOpt[] = [];
|
|
19
|
+
private timeId: number;
|
|
20
|
+
/**加载gif并进行缓存 , 避免重复请求 url */
|
|
21
|
+
public async loadGIF(opt: CanvasGifOpt, ctx: CanvasRenderingContext2D) {
|
|
22
|
+
let { url } = opt;
|
|
23
|
+
let cache = this.gifCache[url];
|
|
24
|
+
this.CTX = ctx;
|
|
25
|
+
if (!cache) {
|
|
26
|
+
/**缓存中没有该url的数据,初始化为请求状态 */
|
|
27
|
+
this.gifCache[url] = { status: 0, data: null, frameList: [] };
|
|
28
|
+
cache = this.gifCache[url];
|
|
29
|
+
try {
|
|
30
|
+
/**设置状态为正在请求 */
|
|
31
|
+
cache.status = 1;
|
|
32
|
+
const data = await this.fetchGIF(url);
|
|
33
|
+
const stream = new Stream(data);
|
|
34
|
+
/**设置状态为请求完毕 */
|
|
35
|
+
cache.status = 2;
|
|
36
|
+
this.parseHeader(url, stream);
|
|
37
|
+
this.parseBlock(opt, stream);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error loading GIF:', error);
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
if (cache.status === 1) {
|
|
43
|
+
/**记录状态为1的opt */
|
|
44
|
+
this.opts.push(opt);
|
|
45
|
+
clearTimeout(this.timeId)
|
|
46
|
+
this.timeId = setTimeout(() => {
|
|
47
|
+
console.log(this.opts)
|
|
48
|
+
let opts = this.opts; this.opts = [];
|
|
49
|
+
opts.forEach(e => this.loadGIF(e, this.CTX));
|
|
50
|
+
}, 100)
|
|
51
|
+
} else if (cache.status === 2) {
|
|
52
|
+
/**状态请求完毕,直接播放 GIF */
|
|
53
|
+
this.stopGif(opt);
|
|
54
|
+
this.playGif(opt);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
private fetchGIF(url: string): Promise<ArrayBuffer> {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const xhr = new XMLHttpRequest();
|
|
61
|
+
xhr.open('GET', url, true);
|
|
62
|
+
// 浏览器兼容处理
|
|
63
|
+
if ('overrideMimeType' in xhr) {
|
|
64
|
+
xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|
65
|
+
}
|
|
66
|
+
xhr.onload = () => {
|
|
67
|
+
if (xhr.status === 200) {
|
|
68
|
+
const data = xhr.response;
|
|
69
|
+
if (data.toString().indexOf('ArrayBuffer') > 0) {
|
|
70
|
+
resolve(new Uint8Array(data));
|
|
71
|
+
} else {
|
|
72
|
+
resolve(data);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
reject(new Error('XHR Error - Response'));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
xhr.onerror = () => {
|
|
79
|
+
reject(new Error('XHR Error'));
|
|
80
|
+
};
|
|
81
|
+
xhr.send();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**解析数据流头部并设置工具canvas的宽高 */
|
|
85
|
+
private parseHeader(url: any, stream: any): void {
|
|
86
|
+
let STREAM = stream, GIF_INFO: GifInfo = this.gifInfo = Object.create(null), canvas = this.canvasTool;
|
|
87
|
+
/**gif文件标识 */
|
|
88
|
+
GIF_INFO.sig = STREAM.read(3);
|
|
89
|
+
/**版本号 */
|
|
90
|
+
GIF_INFO.ver = STREAM.read(3);
|
|
91
|
+
if (GIF_INFO.sig !== 'GIF') throw new Error('Not a GIF file.');
|
|
92
|
+
GIF_INFO.width = STREAM.readUnsigned();
|
|
93
|
+
GIF_INFO.height = STREAM.readUnsigned();
|
|
94
|
+
let bits = this.byteToBitArr(STREAM.readByte());
|
|
95
|
+
GIF_INFO.gctFlag = !!bits.shift();
|
|
96
|
+
GIF_INFO.colorRes = this.bitsToNum(bits.splice(0, 3));
|
|
97
|
+
GIF_INFO.sorted = !!bits.shift();
|
|
98
|
+
GIF_INFO.gctSize = this.bitsToNum(bits.splice(0, 3));
|
|
99
|
+
GIF_INFO.bgColor = STREAM.readByte();
|
|
100
|
+
GIF_INFO.pixelAspectRatio = STREAM.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
|
|
101
|
+
if (GIF_INFO.gctFlag) {
|
|
102
|
+
GIF_INFO.gct = this.parseCT(1 << (GIF_INFO.gctSize + 1), stream);
|
|
103
|
+
}
|
|
104
|
+
// 给 canvas 设置大小
|
|
105
|
+
canvas.width = GIF_INFO.width;
|
|
106
|
+
canvas.height = GIF_INFO.height;
|
|
107
|
+
canvas.style.width = GIF_INFO.width + 'px';
|
|
108
|
+
canvas.style.height = GIF_INFO.height + 'px';
|
|
109
|
+
canvas.getContext('2d')!.setTransform(1, 0, 0, 1, 0, 0);
|
|
110
|
+
};
|
|
111
|
+
/**解析内容块 */
|
|
112
|
+
private parseBlock(opt: CanvasGifOpt, stream: any) {
|
|
113
|
+
let block: GifBlock = Object.create(null), STREAM = stream;
|
|
114
|
+
block.sentinel = STREAM.readByte();
|
|
115
|
+
switch (String.fromCharCode(block.sentinel)) { // For ease of matching
|
|
116
|
+
case '!':
|
|
117
|
+
block.type = 'ext';
|
|
118
|
+
this.parseExt(block, stream, opt.url);
|
|
119
|
+
break;
|
|
120
|
+
case ',':
|
|
121
|
+
/**图像标识符以','(0x2c)作为开始标志 */
|
|
122
|
+
block.type = 'img';
|
|
123
|
+
this.parseImg(block, stream, opt.url);
|
|
124
|
+
break;
|
|
125
|
+
case ';':
|
|
126
|
+
block.type = 'eof';
|
|
127
|
+
// 已经结束啦。结束就跑这
|
|
128
|
+
this.playGif(opt);
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
throw new Error('Unknown block: 0x' + block.sentinel.toString(16));
|
|
132
|
+
}
|
|
133
|
+
// 递归
|
|
134
|
+
if (block.type !== 'eof') {
|
|
135
|
+
this.parseBlock(opt, stream);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/**播放gif */
|
|
139
|
+
private playGif(opt: CanvasGifOpt, index: number = 0) {
|
|
140
|
+
const that = this, { delay = 0 } = opt;
|
|
141
|
+
const { frameList: list } = that.gifCache[opt.url], len = list.length;
|
|
142
|
+
let lastTimestamp: number | undefined;
|
|
143
|
+
const animate = (timestamp: number) => {
|
|
144
|
+
if (lastTimestamp === undefined) lastTimestamp = timestamp;
|
|
145
|
+
const elapsed = timestamp - (lastTimestamp || timestamp);
|
|
146
|
+
if (elapsed >= delay) {
|
|
147
|
+
lastTimestamp = timestamp;
|
|
148
|
+
index++;
|
|
149
|
+
if (index >= len) {
|
|
150
|
+
index = 0;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
that.drawFrame(opt, index);
|
|
154
|
+
that.aniIds[opt.id] = requestAnimationFrame(animate);
|
|
155
|
+
};
|
|
156
|
+
that.aniIds[opt.id] = requestAnimationFrame(animate);
|
|
157
|
+
}
|
|
158
|
+
/**绘制每一帧 */
|
|
159
|
+
private drawFrame(opt: CanvasGifOpt, index: number) {
|
|
160
|
+
const that = this, ctx = that.CTX;
|
|
161
|
+
let { point, points = [], size = [100, 100], url, sizeo, posX = 0, posY = 0, left = 0, top = 0, rotate = 0, alpha = 1, delay } = opt;
|
|
162
|
+
let { frameList: list } = that.gifCache[opt.url];
|
|
163
|
+
that.canvasTool.getContext("2d")!.putImageData(list[index].data, 0, 0);
|
|
164
|
+
// ctx.globalCompositeOperation = "copy";
|
|
165
|
+
let imgEle = that.canvasTool;
|
|
166
|
+
let sizeX: number = size[0],
|
|
167
|
+
sizeY: number = size[1],
|
|
168
|
+
sizeOX = sizeo && sizeo[0],
|
|
169
|
+
sizeOY = sizeo && sizeo[1];
|
|
170
|
+
if (point) points = [...points, point];
|
|
171
|
+
for (let i = 0; i < points.length; i++) {
|
|
172
|
+
const e = points[i],
|
|
173
|
+
x = e[0],
|
|
174
|
+
y = e[1];
|
|
175
|
+
rotate = (rotate * Math.PI) / 180;
|
|
176
|
+
ctx.globalAlpha = alpha;
|
|
177
|
+
ctx.setTransform(1, 0, 0, 1, x, y);
|
|
178
|
+
ctx.rotate(rotate);
|
|
179
|
+
if (sizeOX && sizeOY) {
|
|
180
|
+
/**-sizeX/2 和-sizeY/2确定了图片的中心位置在x,y点 */
|
|
181
|
+
ctx.drawImage(imgEle, posX, posY, sizeOX, sizeOY, -sizeX / 2 + left, -sizeY / 2 + top, sizeX, sizeY);
|
|
182
|
+
} else {
|
|
183
|
+
/**-sizeX/2 和-sizeY/2确定了图片的中心位置在x,y点 */
|
|
184
|
+
ctx.drawImage(imgEle, -sizeX / 2 + left, -sizeY / 2 + top, sizeX, sizeY);
|
|
185
|
+
}
|
|
186
|
+
ctx.rotate(-rotate);
|
|
187
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**关闭之前的定时动画 */
|
|
191
|
+
private stopGif(opt: CanvasGifOpt) {
|
|
192
|
+
const that = this, aniId = that.aniIds[opt.id];
|
|
193
|
+
if (!aniId) return;
|
|
194
|
+
cancelAnimationFrame(aniId);
|
|
195
|
+
that.aniIds[opt.id] = null;
|
|
196
|
+
}
|
|
197
|
+
// 解析
|
|
198
|
+
private parseExt(block: any, stream: any, url: any) {
|
|
199
|
+
let STREAM = stream
|
|
200
|
+
let parseGCExt = (block: any) => {
|
|
201
|
+
STREAM.readByte(); // Always 4 这个必须得这样执行一次
|
|
202
|
+
var bits = this.byteToBitArr(STREAM.readByte());
|
|
203
|
+
block.reserved = bits.splice(0, 3); // Reserved; should be 000.
|
|
204
|
+
block.disposalMethod = this.bitsToNum(bits.splice(0, 3));
|
|
205
|
+
this.LAST_DISPOSA_METHOD = block.disposalMethod
|
|
206
|
+
|
|
207
|
+
block.userInput = bits.shift();
|
|
208
|
+
block.transparencyGiven = bits.shift();
|
|
209
|
+
block.delayTime = STREAM.readUnsigned();
|
|
210
|
+
block.transparencyIndex = STREAM.readByte();
|
|
211
|
+
block.terminator = STREAM.readByte();
|
|
212
|
+
this.pushFrame(block.delayTime, url);
|
|
213
|
+
this.TRANSPARENCY = block.transparencyGiven ? block.transparencyIndex : null;
|
|
214
|
+
};
|
|
215
|
+
let parseComExt = (block: any) => {
|
|
216
|
+
block.comment = this.readSubBlocks(stream);
|
|
217
|
+
};
|
|
218
|
+
let parsePTExt = (block: any) => {
|
|
219
|
+
// No one *ever* uses this. If you use it, deal with parsing it yourself.
|
|
220
|
+
STREAM.readByte(); // Always 12 这个必须得这样执行一次
|
|
221
|
+
block.ptHeader = STREAM.readBytes(12);
|
|
222
|
+
block.ptData = this.readSubBlocks(stream);
|
|
223
|
+
};
|
|
224
|
+
let parseAppExt = (block: any) => {
|
|
225
|
+
var parseNetscapeExt = function (block: any) {
|
|
226
|
+
STREAM.readByte(); // Always 3 这个必须得这样执行一次
|
|
227
|
+
block.unknown = STREAM.readByte(); // ??? Always 1? What is this?
|
|
228
|
+
block.iterations = STREAM.readUnsigned();
|
|
229
|
+
block.terminator = STREAM.readByte();
|
|
230
|
+
};
|
|
231
|
+
var parseUnknownAppExt = (block: any) => {
|
|
232
|
+
block.appData = this.readSubBlocks(stream);
|
|
233
|
+
// FIXME: This won't work if a handler wants to match on any identifier.
|
|
234
|
+
// handler.app && handler.app[block.identifier] && handler.app[block.identifier](block);
|
|
235
|
+
};
|
|
236
|
+
STREAM.readByte(); // Always 11 这个必须得这样执行一次
|
|
237
|
+
block.identifier = STREAM.read(8);
|
|
238
|
+
block.authCode = STREAM.read(3);
|
|
239
|
+
switch (block.identifier) {
|
|
240
|
+
case 'NETSCAPE':
|
|
241
|
+
parseNetscapeExt(block);
|
|
242
|
+
break;
|
|
243
|
+
default:
|
|
244
|
+
parseUnknownAppExt(block);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
let parseUnknownExt = (block: any) => {
|
|
249
|
+
block.data = this.readSubBlocks(stream);
|
|
250
|
+
};
|
|
251
|
+
block.label = STREAM.readByte();
|
|
252
|
+
switch (block.label) {
|
|
253
|
+
case 0xF9:
|
|
254
|
+
block.extType = 'gce';
|
|
255
|
+
parseGCExt(block);
|
|
256
|
+
break;
|
|
257
|
+
case 0xFE:
|
|
258
|
+
block.extType = 'com';
|
|
259
|
+
parseComExt(block);
|
|
260
|
+
break;
|
|
261
|
+
case 0x01:
|
|
262
|
+
block.extType = 'pte';
|
|
263
|
+
parsePTExt(block);
|
|
264
|
+
break;
|
|
265
|
+
case 0xFF:
|
|
266
|
+
block.extType = 'app';
|
|
267
|
+
parseAppExt(block);
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
block.extType = 'unknown';
|
|
271
|
+
parseUnknownExt(block);
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
private pushFrame(delay: any, url: any) {
|
|
276
|
+
let FRAME_LIST = this.gifCache[url].frameList;
|
|
277
|
+
if (!this.ctx) {
|
|
278
|
+
return
|
|
279
|
+
};
|
|
280
|
+
FRAME_LIST.push({
|
|
281
|
+
delay,
|
|
282
|
+
data: this.ctx.getImageData(0, 0, this.gifInfo.width, this.gifInfo.height)
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
private parseImg(img: GifImg, stream: any, url: any) {
|
|
286
|
+
let STREAM = stream;
|
|
287
|
+
function deinterlace(pixels: any, width: any) {
|
|
288
|
+
// Of course this defeats the purpose of interlacing. And it's *probably*
|
|
289
|
+
// the least efficient way it's ever been implemented. But nevertheless...
|
|
290
|
+
let newPixels = new Array(pixels.length);
|
|
291
|
+
const rows = pixels.length / width;
|
|
292
|
+
|
|
293
|
+
function cpRow(toRow: any, fromRow: any) {
|
|
294
|
+
const fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
|
|
295
|
+
newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// See appendix E.
|
|
299
|
+
const offsets = [0, 4, 2, 1],
|
|
300
|
+
steps = [8, 8, 4, 2];
|
|
301
|
+
|
|
302
|
+
let fromRow = 0;
|
|
303
|
+
for (var pass = 0; pass < 4; pass++) {
|
|
304
|
+
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
|
|
305
|
+
cpRow(toRow, fromRow)
|
|
306
|
+
fromRow++;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return newPixels;
|
|
311
|
+
};
|
|
312
|
+
img.leftPos = STREAM.readUnsigned();
|
|
313
|
+
img.topPos = STREAM.readUnsigned();
|
|
314
|
+
img.width = STREAM.readUnsigned();
|
|
315
|
+
img.height = STREAM.readUnsigned();
|
|
316
|
+
let bits = this.byteToBitArr(STREAM.readByte());
|
|
317
|
+
img.lctFlag = bits.shift();
|
|
318
|
+
img.interlaced = bits.shift();
|
|
319
|
+
img.sorted = bits.shift();
|
|
320
|
+
img.reserved = bits.splice(0, 2);
|
|
321
|
+
img.lctSize = this.bitsToNum(bits.splice(0, 3));
|
|
322
|
+
if (img.lctFlag) {
|
|
323
|
+
img.lct = this.parseCT(1 << (img.lctSize + 1), stream);
|
|
324
|
+
}
|
|
325
|
+
img.lzwMinCodeSize = STREAM.readByte();
|
|
326
|
+
const lzwData = this.readSubBlocks(stream);
|
|
327
|
+
img.pixels = this.lzwDecode(img.lzwMinCodeSize, lzwData);
|
|
328
|
+
if (img.interlaced) { // Move
|
|
329
|
+
img.pixels = deinterlace(img.pixels, img.width);
|
|
330
|
+
}
|
|
331
|
+
this.doImg(img, url);
|
|
332
|
+
};
|
|
333
|
+
/**读取数据块 */
|
|
334
|
+
private readSubBlocks(stream: any): string {
|
|
335
|
+
let size: number, STREAM = stream,
|
|
336
|
+
// let size: number, STREAM = this.stream,
|
|
337
|
+
data = '';
|
|
338
|
+
do {
|
|
339
|
+
size = STREAM.readByte();
|
|
340
|
+
data += STREAM.read(size);
|
|
341
|
+
} while (size !== 0);
|
|
342
|
+
return data;
|
|
343
|
+
};
|
|
344
|
+
/**解码LZW编码 */
|
|
345
|
+
private lzwDecode(minCodeSize: number, data: string): number[] {
|
|
346
|
+
// TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
|
|
347
|
+
// Maybe this streaming thing should be merged with the Stream?
|
|
348
|
+
let pos = 0;
|
|
349
|
+
function readCode(size: number) {
|
|
350
|
+
let code = 0;
|
|
351
|
+
for (let i = 0; i < size; i++) {
|
|
352
|
+
if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) {
|
|
353
|
+
code |= (1 << i);
|
|
354
|
+
}
|
|
355
|
+
pos++;
|
|
356
|
+
}
|
|
357
|
+
return code;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
let output: any[] = [],
|
|
361
|
+
clearCode = 1 << minCodeSize,
|
|
362
|
+
eoiCode = clearCode + 1,
|
|
363
|
+
codeSize = minCodeSize + 1,
|
|
364
|
+
dict: any[] = [];
|
|
365
|
+
|
|
366
|
+
function clear() {
|
|
367
|
+
dict = [];
|
|
368
|
+
codeSize = minCodeSize + 1;
|
|
369
|
+
for (let i = 0; i < clearCode; i++) {
|
|
370
|
+
dict[i] = [i];
|
|
371
|
+
}
|
|
372
|
+
dict[clearCode] = [];
|
|
373
|
+
dict[eoiCode] = null;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
let code: any = null,
|
|
377
|
+
last: any = null;
|
|
378
|
+
while (true) {
|
|
379
|
+
last = code!;
|
|
380
|
+
code = readCode(codeSize);
|
|
381
|
+
|
|
382
|
+
if (code === clearCode) {
|
|
383
|
+
clear();
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (code === eoiCode) {
|
|
387
|
+
break
|
|
388
|
+
};
|
|
389
|
+
if (code < dict.length) {
|
|
390
|
+
if (last !== clearCode) {
|
|
391
|
+
dict.push(dict[last].concat(dict[code][0]));
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
if (code !== dict.length) {
|
|
395
|
+
throw new Error('Invalid LZW code.');
|
|
396
|
+
}
|
|
397
|
+
dict.push(dict[last].concat(dict[last][0]));
|
|
398
|
+
}
|
|
399
|
+
output.push.apply(output, dict[code]);
|
|
400
|
+
|
|
401
|
+
if (dict.length === (1 << codeSize) && codeSize < 12) {
|
|
402
|
+
// If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
|
|
403
|
+
codeSize++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return output;
|
|
407
|
+
};
|
|
408
|
+
/** */
|
|
409
|
+
private doImg(img: GifImg, url: string) {
|
|
410
|
+
let TEMP_CANVAS_CTX = this.ctx, TEMP_CANVAS = this.canvasTool, GIF_INFO = this.gifInfo;
|
|
411
|
+
let FRAME_LIST = this.gifCache[url].frameList;
|
|
412
|
+
if (!this.ctx) {
|
|
413
|
+
// 没有就创建
|
|
414
|
+
TEMP_CANVAS_CTX = this.ctx = TEMP_CANVAS.getContext('2d')!;
|
|
415
|
+
}
|
|
416
|
+
const currIdx = FRAME_LIST.length,
|
|
417
|
+
ct = img.lctFlag ? img.lct : GIF_INFO.gct;
|
|
418
|
+
if (currIdx > 0) {
|
|
419
|
+
// 这块不要动
|
|
420
|
+
if (this.LAST_DISPOSA_METHOD === 3) {
|
|
421
|
+
// Restore to previous
|
|
422
|
+
// If we disposed every TEMP_CANVAS_CTX including first TEMP_CANVAS_CTX up to this point, then we have
|
|
423
|
+
// no composited TEMP_CANVAS_CTX to restore to. In this case, restore to background instead.
|
|
424
|
+
if (this.CURRENT_FRAME_INDEX !== null && this.CURRENT_FRAME_INDEX > -1) {
|
|
425
|
+
TEMP_CANVAS_CTX.putImageData(FRAME_LIST[this.CURRENT_FRAME_INDEX].data, 0, 0);
|
|
426
|
+
} else {
|
|
427
|
+
TEMP_CANVAS_CTX.clearRect(0, 0, TEMP_CANVAS.width, TEMP_CANVAS.height);
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
this.CURRENT_FRAME_INDEX = currIdx - 1;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (this.LAST_DISPOSA_METHOD === 2) {
|
|
434
|
+
// Restore to background color
|
|
435
|
+
// Browser implementations historically restore to transparent; we do the same.
|
|
436
|
+
// http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079
|
|
437
|
+
TEMP_CANVAS_CTX.clearRect(0, 0, TEMP_CANVAS.width, TEMP_CANVAS.height);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
let imgData = TEMP_CANVAS_CTX.getImageData(img.leftPos, img.topPos, img.width, img.height);
|
|
441
|
+
img.pixels.forEach((pixel: any, i: number) => {
|
|
442
|
+
if (pixel !== this.TRANSPARENCY) {
|
|
443
|
+
imgData.data[i * 4 + 0] = ct[pixel][0];
|
|
444
|
+
imgData.data[i * 4 + 1] = ct[pixel][1];
|
|
445
|
+
imgData.data[i * 4 + 2] = ct[pixel][2];
|
|
446
|
+
imgData.data[i * 4 + 3] = 255; // Opaque.
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
TEMP_CANVAS_CTX.putImageData(imgData, img.leftPos, img.topPos);
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/**数字转换为对应的位然后变为长度为7的boolean数组
|
|
453
|
+
* @param bite number
|
|
454
|
+
*/
|
|
455
|
+
private byteToBitArr(bite: number): boolean[] {
|
|
456
|
+
let byteArr: boolean[] = [];
|
|
457
|
+
for (let i = 7; i >= 0; i--) {
|
|
458
|
+
byteArr.push(!!(bite & (1 << i)));
|
|
459
|
+
}
|
|
460
|
+
return byteArr;
|
|
461
|
+
};
|
|
462
|
+
/**boolean数组转换为对应的数字
|
|
463
|
+
* @param ba boolean[]
|
|
464
|
+
*/
|
|
465
|
+
private bitsToNum(ba: boolean[]): number {
|
|
466
|
+
return ba.reduce(function (s, n) {
|
|
467
|
+
return s * 2 + Number(n);
|
|
468
|
+
}, 0);
|
|
469
|
+
};
|
|
470
|
+
/**获取全局颜色列表
|
|
471
|
+
* @param size 全局颜色列表大小
|
|
472
|
+
*/
|
|
473
|
+
private parseCT(size: number, stream: Stream): [number, number, number][] { // Each entry is 3 bytes, for RGB.
|
|
474
|
+
let ct: [number, number, number][] = [];
|
|
475
|
+
for (let i = 0; i < size; i++) {
|
|
476
|
+
ct.push(stream.readBytes(3) as [number, number, number]);
|
|
477
|
+
}
|
|
478
|
+
return ct;
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**数据流解析类 */
|
|
483
|
+
class Stream {
|
|
484
|
+
constructor(data: any) {
|
|
485
|
+
this.data = data;
|
|
486
|
+
this.len = data.length;
|
|
487
|
+
this.pos = 0;
|
|
488
|
+
}
|
|
489
|
+
/**数据流 */
|
|
490
|
+
private data: string | Uint8Array;
|
|
491
|
+
/**数据流长度 */
|
|
492
|
+
private len: number;
|
|
493
|
+
/**数据流现在读取的位置 */
|
|
494
|
+
private pos: number = 0;
|
|
495
|
+
/**读取一字节(8位)的数据 */
|
|
496
|
+
public readByte(): number {
|
|
497
|
+
if (this.pos >= this.data.length) {
|
|
498
|
+
throw new Error('Attempted to read past end of stream.');
|
|
499
|
+
}
|
|
500
|
+
if (this.data instanceof Uint8Array)
|
|
501
|
+
return this.data[this.pos++];
|
|
502
|
+
else
|
|
503
|
+
return this.data.charCodeAt(this.pos++) & 0xFF;
|
|
504
|
+
};
|
|
505
|
+
/**读取指定长度的数据 */
|
|
506
|
+
public readBytes(n: number): number[] {
|
|
507
|
+
let bytes: number[] = [];
|
|
508
|
+
for (let i = 0; i < n; i++) {
|
|
509
|
+
bytes.push(this.readByte());
|
|
510
|
+
}
|
|
511
|
+
return bytes;
|
|
512
|
+
};
|
|
513
|
+
/**获取指定长度字符串 */
|
|
514
|
+
public read(n: number): string {
|
|
515
|
+
let chars = '';
|
|
516
|
+
for (let i = 0; i < n; i++) {
|
|
517
|
+
chars += String.fromCharCode(this.readByte());
|
|
518
|
+
}
|
|
519
|
+
return chars;
|
|
520
|
+
};
|
|
521
|
+
/**读取无符号数据2字节 最大:255<<8 + 255 */
|
|
522
|
+
public readUnsigned(): number { // Little-endian.
|
|
523
|
+
let unsigned = this.readBytes(2);
|
|
524
|
+
return (unsigned[1] << 8) + unsigned[0];
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**canvas绘制gif的配置 */
|
|
529
|
+
interface CanvasGifOpt extends CanvasGif {
|
|
530
|
+
size: [number, number];
|
|
531
|
+
point: [number, number];
|
|
532
|
+
points: [number, number][];
|
|
533
|
+
/**gif每一帧延迟的时间,控制整体播放速度 */
|
|
534
|
+
delay?: number;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**Gif信息(属性顺序就是数据流顺序)*/
|
|
538
|
+
interface GifInfo {
|
|
539
|
+
/**(3字节)gif文件标识*/
|
|
540
|
+
sig: string;
|
|
541
|
+
/**(3字节)版本*/
|
|
542
|
+
ver: string;
|
|
543
|
+
/**(2字节)像素宽*/
|
|
544
|
+
width: number;
|
|
545
|
+
/**(2字节)像素高*/
|
|
546
|
+
height: number;
|
|
547
|
+
/**(1位)全局颜色列表标志(Global Color Table Flag),当置位时表示有全局颜色列表,pixel值有意义*/
|
|
548
|
+
gctFlag: boolean;
|
|
549
|
+
/**(3位)颜色深度(Color ResoluTion),cr+1确定图象的颜色深度*/
|
|
550
|
+
colorRes: number;
|
|
551
|
+
/**(1位)分类标志(Sort Flag),如果置位表示全局颜色列表分类排列*/
|
|
552
|
+
sorted: boolean;
|
|
553
|
+
/**(3位)全局颜色列表大小,pixel+1确定颜色列表的索引数=(2^(pixel+1))*/
|
|
554
|
+
gctSize: number;
|
|
555
|
+
/**(1字节)背景颜色:背景颜色在全局颜色列表中的索引(PS:是索引而不是RGB值,所以如果没有全局颜色列表时,该值没有意义)*/
|
|
556
|
+
bgColor: number;
|
|
557
|
+
/**(1字节)全局像素的宽度与高度的比值 */
|
|
558
|
+
pixelAspectRatio: number;
|
|
559
|
+
/**全局颜色列表 [R,G,B][]*/
|
|
560
|
+
gct: number[][];
|
|
561
|
+
}
|
|
562
|
+
/**图像标识符 */
|
|
563
|
+
interface GifImg {
|
|
564
|
+
/**(2字节)X方向偏移量 */
|
|
565
|
+
leftPos: number;
|
|
566
|
+
/**(2字节)Y方向偏移量 */
|
|
567
|
+
topPos: number;
|
|
568
|
+
/**(2字节)图像宽 */
|
|
569
|
+
width: number;
|
|
570
|
+
/**(2字节)图像高 */
|
|
571
|
+
height: number;
|
|
572
|
+
/**(1位)局部颜色列表标志(Local Color Table Flag) 置位时标识紧接在图象标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图象使用;值否时使用全局颜色列表,忽略pixel值。 */
|
|
573
|
+
lctFlag?: boolean;
|
|
574
|
+
/**(1位)交织标志(Interlace Flag),置位时图象数据使用交织方式排列,否则使用顺序排列 */
|
|
575
|
+
interlaced?: boolean;
|
|
576
|
+
/**(1位)分类标志(Sort Flag),如果置位表示紧跟着的局部颜色列表分类排列 */
|
|
577
|
+
sorted?: boolean;
|
|
578
|
+
/**(2位)保留,必须初始化为0 */
|
|
579
|
+
reserved: boolean[];
|
|
580
|
+
/**(3位)局部颜色列表大小(Size of Local Color Table),pixel+1就为颜色列表的位数 */
|
|
581
|
+
lctSize: number;
|
|
582
|
+
lct: number[][];
|
|
583
|
+
/**(1字节)LZW编码长度 */
|
|
584
|
+
lzwMinCodeSize: number;
|
|
585
|
+
/** */
|
|
586
|
+
pixels: any
|
|
587
|
+
}
|
|
588
|
+
interface GifBlock extends GifImg {
|
|
589
|
+
sentinel: number;
|
|
590
|
+
type: string;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
interface GifCache {
|
|
594
|
+
/**status资源请求的状态0未请求1请求中2已请求 */
|
|
595
|
+
status: 0 | 1 | 2,
|
|
596
|
+
/**data缓存的gif数据 */
|
|
597
|
+
data: ArrayBuffer | null,
|
|
598
|
+
/**frameList存放每一帧以及对应的延时 */
|
|
599
|
+
frameList: { delay: number, data: ImageData }[]
|
|
600
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export class SLUCanvasImg {
|
|
2
|
+
/**图片的缓存 */
|
|
3
|
+
static readonly ImageCache: { [key: string]: HTMLImageElement } = Object.create(null);
|
|
4
|
+
/**加载需要提前加载的异步图片,保证图片层级正确 */
|
|
5
|
+
public static loadImg(urls: string[] = ['/assets/images/map/map_selected.png']) {
|
|
6
|
+
urls.forEach((url) => this.getImgPromise(url));
|
|
7
|
+
}
|
|
8
|
+
/**绘制图片,默认图片中心点 */
|
|
9
|
+
public static async drawImg(img: SLTCanvas.Image, ctx: CanvasRenderingContext2D): Promise<void> {
|
|
10
|
+
if (img.ifHide === true) return;
|
|
11
|
+
let { point, points = [], size = [0, 0], url, sizeo, posX = 0, posY = 0, left = 0, top = 0, rotate = 0, alpha = 1 } = img;
|
|
12
|
+
let sizeX: number = size[0],
|
|
13
|
+
sizeY: number = size[1],
|
|
14
|
+
sizeOX = sizeo && sizeo[0],
|
|
15
|
+
sizeOY = sizeo && sizeo[1];
|
|
16
|
+
let imgEle = this.ImageCache[url] || (await this.getImgPromise(url));
|
|
17
|
+
if (point) points = [...points, point];
|
|
18
|
+
for (let i = 0; i < points.length; i++) {
|
|
19
|
+
const e = points[i],
|
|
20
|
+
x = e[0],
|
|
21
|
+
y = e[1];
|
|
22
|
+
rotate = (rotate * Math.PI) / 180;
|
|
23
|
+
ctx.globalAlpha = alpha;
|
|
24
|
+
ctx.setTransform(1, 0, 0, 1, x, y);
|
|
25
|
+
ctx.rotate(rotate);
|
|
26
|
+
if (sizeOX && sizeOY) {
|
|
27
|
+
/**-sizeX/2 和-sizeY/2确定了图片的中心位置在x,y点 */
|
|
28
|
+
ctx.drawImage(imgEle, posX, posY, sizeOX, sizeOY, -sizeX / 2 + left, -sizeY / 2 + top, sizeX, sizeY);
|
|
29
|
+
} else {
|
|
30
|
+
/**-sizeX/2 和-sizeY/2确定了图片的中心位置在x,y点 */
|
|
31
|
+
ctx.drawImage(imgEle, -sizeX / 2 + left, -sizeY / 2 + top, sizeX, sizeY);
|
|
32
|
+
}
|
|
33
|
+
ctx.rotate(-rotate);
|
|
34
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**根据图片路径地址,获取图片后缓存 , 避免重复请求
|
|
38
|
+
* @param url 图片路径
|
|
39
|
+
*/
|
|
40
|
+
private static getImgPromise(url: string): Promise<HTMLImageElement> {
|
|
41
|
+
let img = this.ImageCache[url];
|
|
42
|
+
if (!img) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
let img = new Image();
|
|
45
|
+
img.onload = () => {
|
|
46
|
+
this.ImageCache[url] = img;
|
|
47
|
+
resolve(img);
|
|
48
|
+
};
|
|
49
|
+
img.src = `${url}`;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return Promise.resolve(img);
|
|
53
|
+
}
|
|
54
|
+
}
|