-
Notifications
You must be signed in to change notification settings - Fork 120
/
Copy pathindex.web.ts
136 lines (117 loc) · 3.58 KB
/
index.web.ts
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
import type { ImageCropData, CropResult } from './types.ts';
const ERROR_PREFIX = 'ImageEditor: ';
function drawImage(
img: HTMLImageElement | ImageBitmap,
{ offset, size, displaySize }: ImageCropData
): HTMLCanvasElement {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
if (!context) {
throw new Error(ERROR_PREFIX + 'Failed to get canvas context');
}
const sx = offset.x,
sy = offset.y,
sWidth = size.width,
sHeight = size.height,
dx = 0,
dy = 0,
dWidth = displaySize?.width ?? sWidth,
dHeight = displaySize?.height ?? sHeight;
canvas.width = dWidth;
canvas.height = dHeight;
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
return canvas;
}
function fetchImage(
imgSrc: string,
headers: ImageCropData['headers']
): Promise<HTMLImageElement | ImageBitmap> {
if (headers) {
return fetch(imgSrc, {
method: 'GET',
headers: new Headers(headers),
})
.then((response) => {
if (!response.ok) {
throw new Error(
ERROR_PREFIX +
'Failed to fetch the image: ' +
imgSrc +
'. Request failed with status: ' +
response.status
);
}
return response.blob();
})
.then((blob) => createImageBitmap(blob));
}
return new Promise<HTMLImageElement>((resolve, reject) => {
const onceOptions = { once: true };
const img = new Image();
function onImageError(event: ErrorEvent) {
reject(event);
}
function onLoad() {
resolve(img);
}
img.addEventListener('error', onImageError, onceOptions);
img.addEventListener('load', onLoad, onceOptions);
img.crossOrigin = 'anonymous';
img.src = imgSrc;
});
}
const DEFAULT_COMPRESSION_QUALITY = 0.9;
class ImageEditor {
static cropImage(
imgSrc: string,
cropData: ImageCropData
): Promise<CropResult> {
/**
* Returns a promise that resolves with the base64 encoded string of the cropped image
*/
return fetchImage(imgSrc, cropData.headers).then(
function onfulfilledImgToCanvas(image) {
const ext = cropData.format ?? 'jpeg';
const type = `image/${ext}`;
const quality = cropData.quality ?? DEFAULT_COMPRESSION_QUALITY;
const canvas = drawImage(image, cropData);
return new Promise<Blob | null>(function onfulfilledCanvasToBlob(
resolve
) {
canvas.toBlob(resolve, type, quality);
}).then((blob) => {
if (!blob) {
throw new Error('Image cannot be created from canvas');
}
let _path: string, _uri: string;
const result: CropResult = {
width: canvas.width,
height: canvas.height,
name: 'ReactNative_cropped_image.' + ext,
type: ('image/' + ext) as CropResult['type'],
size: blob.size,
// Lazy getters to avoid unnecessary memory usage
get path() {
if (!_path) {
_path = URL.createObjectURL(blob);
}
return _path;
},
get uri() {
return result.base64 as string;
},
get base64() {
if (!_uri) {
_uri = canvas.toDataURL(type, quality);
}
return _uri.split(',')[1];
// ^^^ remove `data:image/xxx;base64,` prefix (to align with iOS/Android platform behavior)
},
};
return result;
});
}
);
}
}
export default ImageEditor;