yummies 7.11.0 → 7.12.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/async.cjs +162 -48
- package/async.cjs.map +1 -1
- package/async.d.ts +108 -7
- package/async.js +163 -54
- package/async.js.map +1 -1
- package/chunk-CVq3Gv4J.cjs +50 -0
- package/chunk-YKewjYmz.js +37 -0
- package/common.cjs +48 -8
- package/common.cjs.map +1 -1
- package/common.d.ts +35 -2
- package/common.js +49 -11
- package/common.js.map +1 -1
- package/complex.cjs +275 -128
- package/complex.cjs.map +1 -1
- package/complex.js +275 -133
- package/complex.js.map +1 -1
- package/cookie.cjs +17 -7
- package/cookie.cjs.map +1 -1
- package/cookie.d.ts +8 -0
- package/cookie.js +18 -9
- package/cookie.js.map +1 -1
- package/css.cjs +147 -39
- package/css.cjs.map +1 -1
- package/css.d.ts +98 -6
- package/css.js +143 -41
- package/css.js.map +1 -1
- package/data.cjs +90 -55
- package/data.cjs.map +1 -1
- package/data.d.ts +32 -0
- package/data.js +91 -61
- package/data.js.map +1 -1
- package/date-time.cjs +578 -412
- package/date-time.cjs.map +1 -1
- package/date-time.d.ts +88 -0
- package/date-time.js +575 -421
- package/date-time.js.map +1 -1
- package/device.cjs +48 -23
- package/device.cjs.map +1 -1
- package/device.d.ts +32 -0
- package/device.js +49 -31
- package/device.js.map +1 -1
- package/encodings.cjs +275 -266
- package/encodings.cjs.map +1 -1
- package/encodings.d.ts +8 -0
- package/encodings.js +276 -268
- package/encodings.js.map +1 -1
- package/errors.cjs +20 -18
- package/errors.cjs.map +1 -1
- package/errors.js +19 -19
- package/errors.js.map +1 -1
- package/file.cjs +42 -24
- package/file.cjs.map +1 -1
- package/file.d.ts +16 -0
- package/file.js +43 -27
- package/file.js.map +1 -1
- package/format.cjs +125 -83
- package/format.cjs.map +1 -1
- package/format.js +118 -82
- package/format.js.map +1 -1
- package/html.cjs +226 -137
- package/html.cjs.map +1 -1
- package/html.d.ts +64 -0
- package/html.js +223 -150
- package/html.js.map +1 -1
- package/id.cjs +74 -17
- package/id.cjs.map +1 -1
- package/id.js +73 -24
- package/id.js.map +1 -1
- package/imports.cjs +41 -29
- package/imports.cjs.map +1 -1
- package/imports.d.ts +8 -0
- package/imports.js +40 -31
- package/imports.js.map +1 -1
- package/math.cjs +32 -6
- package/math.cjs.map +1 -1
- package/math.d.ts +16 -0
- package/math.js +33 -10
- package/math.js.map +1 -1
- package/media.cjs +275 -84
- package/media.cjs.map +1 -1
- package/media.d.ts +188 -2
- package/media.js +274 -93
- package/media.js.map +1 -1
- package/mobx.cjs +353 -193
- package/mobx.cjs.map +1 -1
- package/mobx.d.ts +7 -0
- package/mobx.js +351 -200
- package/mobx.js.map +1 -1
- package/ms.cjs +21 -10
- package/ms.cjs.map +1 -1
- package/ms.js +22 -13
- package/ms.js.map +1 -1
- package/number.cjs +13 -7
- package/number.cjs.map +1 -1
- package/number.js +14 -9
- package/number.js.map +1 -1
- package/package.json +10 -2
- package/parser.cjs +117 -64
- package/parser.cjs.map +1 -1
- package/parser.js +111 -64
- package/parser.js.map +1 -1
- package/price.cjs +24 -18
- package/price.cjs.map +1 -1
- package/price.d.ts +8 -0
- package/price.js +25 -20
- package/price.js.map +1 -1
- package/random.cjs +79 -13
- package/random.cjs.map +1 -1
- package/random.d.ts +64 -0
- package/random.js +80 -22
- package/random.js.map +1 -1
- package/react.cjs +673 -214
- package/react.cjs.map +1 -1
- package/react.d.ts +21 -0
- package/react.js +674 -239
- package/react.js.map +1 -1
- package/sound.cjs +14 -9
- package/sound.cjs.map +1 -1
- package/sound.js +15 -11
- package/sound.js.map +1 -1
- package/storage.cjs +49 -50
- package/storage.cjs.map +1 -1
- package/storage.d.ts +8 -0
- package/storage.js +50 -53
- package/storage.js.map +1 -1
- package/text.cjs +51 -34
- package/text.cjs.map +1 -1
- package/text.js +52 -37
- package/text.js.map +1 -1
- package/type-guard.cjs +292 -72
- package/type-guard.cjs.map +1 -1
- package/type-guard.js +288 -73
- package/type-guard.js.map +1 -1
- package/types.cjs +0 -2
- package/types.global.cjs +0 -2
- package/types.global.js +0 -2
- package/types.js +0 -2
- package/vibrate.cjs +31 -6
- package/vibrate.cjs.map +1 -1
- package/vibrate.d.ts +23 -1
- package/vibrate.js +32 -8
- package/vibrate.js.map +1 -1
- package/types.cjs.map +0 -1
- package/types.global.cjs.map +0 -1
- package/types.global.js.map +0 -1
- package/types.js.map +0 -1
package/media.js
CHANGED
|
@@ -1,106 +1,287 @@
|
|
|
1
1
|
import { degToRad } from "yummies/math";
|
|
2
|
+
//#region src/media.ts
|
|
3
|
+
/**
|
|
4
|
+
* Reads a {@link Blob} as a **data URL** string (`data:<mime>;base64,...`) using {@link FileReader#readAsDataURL}.
|
|
5
|
+
*
|
|
6
|
+
* Useful for previewing uploads, embedding small assets inline, or serializing binary for APIs that
|
|
7
|
+
* expect Base64-in-JSON. The result includes the MIME prefix, not raw Base64 alone — use
|
|
8
|
+
* {@link decodeDataUrl} if you need the payload and type separately.
|
|
9
|
+
*
|
|
10
|
+
* @param blob - Any `Blob` or `File` (files are blobs).
|
|
11
|
+
* @returns Resolves to the data URL string; rejects if reading fails.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const dataUrl = await blobToBase64(file);
|
|
16
|
+
* previewImg.src = dataUrl;
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const fromFetch = await fetch('/api/export').then((r) => r.blob());
|
|
22
|
+
* const dataUrl = await blobToBase64(fromFetch);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
2
25
|
function blobToBase64(blob) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const reader = new FileReader();
|
|
28
|
+
reader.onloadend = () => resolve(reader.result);
|
|
29
|
+
reader.onerror = reject;
|
|
30
|
+
reader.readAsDataURL(blob);
|
|
31
|
+
});
|
|
9
32
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
/**
|
|
34
|
+
* If `urlOrBlob` is already a string, returns it unchanged. If it is a {@link Blob}, returns
|
|
35
|
+
* `URL.createObjectURL(blob)` — a short-lived `blob:` URL valid in this document until
|
|
36
|
+
* {@link URL.revokeObjectURL} is called.
|
|
37
|
+
*
|
|
38
|
+
* Pair with {@link renderImage} or `<img src>` without re-fetching binary data. **Remember to
|
|
39
|
+
* `revokeObjectURL`** when the URL is no longer needed to avoid retaining blob memory.
|
|
40
|
+
*
|
|
41
|
+
* @param urlOrBlob - Remote/http(s) URL string, data URL, or a `Blob` / `File`.
|
|
42
|
+
* @returns A string suitable for `HTMLImageElement.src` or similar.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const src = blobToUrl(uploadedFile);
|
|
47
|
+
* img.src = src;
|
|
48
|
+
* // when done:
|
|
49
|
+
* URL.revokeObjectURL(src);
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* blobToUrl('https://cdn.example.com/logo.png'); // passed through as-is
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
var blobToUrl = (urlOrBlob) => urlOrBlob instanceof Blob ? URL.createObjectURL(urlOrBlob) : urlOrBlob;
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new {@link Blob} from a {@link File}, copying the bytes and keeping `file.type` as
|
|
60
|
+
* the blob’s MIME type. Handy when an API accepts `Blob` but you only have `File`, or you want a
|
|
61
|
+
* plain blob without the `File` name/lastModified metadata.
|
|
62
|
+
*
|
|
63
|
+
* @param file - Source file from an `<input type="file">` or drag-and-drop.
|
|
64
|
+
* @returns A `Blob` with the same content and `type` as the file.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const blob = fileToBlob(fileFromInput);
|
|
69
|
+
* await uploadEndpoint(blob);
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* const blob = fileToBlob(imageFile);
|
|
75
|
+
* const url = URL.createObjectURL(blob);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
var fileToBlob = (file) => {
|
|
79
|
+
return new Blob([file], { type: file.type });
|
|
13
80
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Draws an {@link HTMLImageElement} onto an offscreen canvas, then builds a {@link Blob} from the
|
|
83
|
+
* raster (via `canvas.toDataURL` + binary decode). Dimensions use `naturalWidth` / `naturalHeight`,
|
|
84
|
+
* falling back to `300×300` if those are zero (e.g. not yet decoded).
|
|
85
|
+
*
|
|
86
|
+
* **CORS:** if the image is cross-origin without proper CORS headers, the canvas may be
|
|
87
|
+
* *tainted* and `toDataURL` can throw — same browser rules as any canvas export.
|
|
88
|
+
*
|
|
89
|
+
* @param imageElement - Loaded image (`complete` / decoded recommended).
|
|
90
|
+
* @param mimeType - Output MIME type, e.g. `'image/png'` (default) or `'image/jpeg'`. Encoder support is browser-dependent.
|
|
91
|
+
* @returns Encoded image as a `Blob` with a matching `type` when possible.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const img = await renderImage('/photo.jpg');
|
|
96
|
+
* const jpegBlob = imageToBlob(img, 'image/jpeg');
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const pngBlob = imageToBlob(cachedImg); // default image/png
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
var imageToBlob = (imageElement, mimeType = "image/png") => {
|
|
105
|
+
const canvas = document.createElement("canvas");
|
|
106
|
+
canvas.width = imageElement.naturalWidth || 300;
|
|
107
|
+
canvas.height = imageElement.naturalHeight || 300;
|
|
108
|
+
canvas.getContext("2d").drawImage(imageElement, 0, 0);
|
|
109
|
+
const dataUri = canvas.toDataURL(mimeType, 1);
|
|
110
|
+
const base64data = dataUri.split(",")[1];
|
|
111
|
+
const base64MimeType = dataUri.split(";")[0].slice(5);
|
|
112
|
+
const bytes = globalThis.atob(base64data);
|
|
113
|
+
const buf = new ArrayBuffer(bytes.length);
|
|
114
|
+
const array = new Uint8Array(buf);
|
|
115
|
+
for (let index = 0; index < bytes.length; index++) array[index] = bytes.charCodeAt(index);
|
|
116
|
+
return new Blob([array], { type: base64MimeType });
|
|
30
117
|
};
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Loads a resource into a new `HTMLImageElement`: `src` is set via {@link blobToUrl} (so blobs get
|
|
120
|
+
* object URLs, strings are used directly). Resolves on `load` with the same element; rejects on
|
|
121
|
+
* `error` (e.g. bad URL, network failure, corrupt image) **with no rejection value**.
|
|
122
|
+
*
|
|
123
|
+
* Does not add the node to the DOM — use the returned element for canvas, measuring, or
|
|
124
|
+
* {@link imageToBlob}.
|
|
125
|
+
*
|
|
126
|
+
* @param urlOrBlob - Remote URL, data URL, or `Blob` / `File`.
|
|
127
|
+
* @returns Promise that fulfills with the loaded `HTMLImageElement`.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* const img = await renderImage('https://example.com/pic.png');
|
|
132
|
+
* document.body.appendChild(img);
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const img = await renderImage(pickedFile);
|
|
138
|
+
* const blob = imageToBlob(img, 'image/webp');
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
var renderImage = (urlOrBlob) => new Promise((resolve, reject) => {
|
|
142
|
+
const image = new Image();
|
|
143
|
+
image.src = blobToUrl(urlOrBlob);
|
|
144
|
+
image.onload = () => resolve(image);
|
|
145
|
+
image.onerror = () => reject();
|
|
36
146
|
});
|
|
37
147
|
function cropImageFromCanvas(context) {
|
|
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
|
-
|
|
148
|
+
const canvas = context.canvas;
|
|
149
|
+
let w = canvas.width;
|
|
150
|
+
let h = canvas.height;
|
|
151
|
+
const pix = {
|
|
152
|
+
x: [],
|
|
153
|
+
y: []
|
|
154
|
+
};
|
|
155
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
156
|
+
let x;
|
|
157
|
+
let y;
|
|
158
|
+
let index;
|
|
159
|
+
for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
|
|
160
|
+
index = (y * w + x) * 4;
|
|
161
|
+
if (imageData.data[index + 3] > 0) {
|
|
162
|
+
pix.x.push(x);
|
|
163
|
+
pix.y.push(y);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
pix.x.sort((a, b) => a - b);
|
|
167
|
+
pix.y.sort((a, b) => a - b);
|
|
168
|
+
const n = pix.x.length - 1;
|
|
169
|
+
w = 1 + pix.x[n] - pix.x[0];
|
|
170
|
+
h = 1 + pix.y[n] - pix.y[0];
|
|
171
|
+
const cut = context.getImageData(pix.x[0], pix.y[0], w, h);
|
|
172
|
+
canvas.width = w;
|
|
173
|
+
canvas.height = h;
|
|
174
|
+
context.putImageData(cut, 0, 0);
|
|
175
|
+
return canvas;
|
|
65
176
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Rotates `image` around its center on a square canvas (side = max(width, height)), then **crops**
|
|
179
|
+
* transparent margins by trimming to the bounding box of pixels with non-zero alpha.
|
|
180
|
+
* Returns a **new** loaded `HTMLImageElement` (PNG data URL under the hood via {@link renderImage}).
|
|
181
|
+
*
|
|
182
|
+
* `angle` is in **degrees**; converted with {@link degToRad} from `yummies/math`.
|
|
183
|
+
*
|
|
184
|
+
* Very large sources can stress memory on some mobile browsers (known iPhone issues — see TODO in source).
|
|
185
|
+
*
|
|
186
|
+
* @param image - Source image (should be decoded; uses `width` / `height`).
|
|
187
|
+
* @param angle - Rotation in degrees (e.g. `90` for quarter turn).
|
|
188
|
+
* @returns Promise of a new `HTMLImageElement` showing the rotated, cropped result.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* const upright = await rotateImage(landscapeImg, 90);
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```ts
|
|
197
|
+
* const fixed = await rotateImage(await renderImage(file), -15);
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
var rotateImage = (image, angle) => {
|
|
201
|
+
const maxSize = Math.max(image.width, image.height);
|
|
202
|
+
const canvas = document.createElement("canvas");
|
|
203
|
+
canvas.width = maxSize;
|
|
204
|
+
canvas.height = maxSize;
|
|
205
|
+
const context = canvas.getContext("2d");
|
|
206
|
+
context.save();
|
|
207
|
+
context.translate(canvas.width / 2, canvas.height / 2);
|
|
208
|
+
context.rotate(degToRad(angle));
|
|
209
|
+
context.drawImage(image, -image.width / 2, -image.height / 2);
|
|
210
|
+
context.restore();
|
|
211
|
+
cropImageFromCanvas(context);
|
|
212
|
+
return renderImage(canvas.toDataURL("image/png"));
|
|
79
213
|
};
|
|
214
|
+
/**
|
|
215
|
+
* Parses a `data:` URL of the form `data:<mime>;base64,<payload>` into its MIME type and raw
|
|
216
|
+
* Base64 body (without the `data:...;base64,` prefix). Non-matching strings yield `undefined`
|
|
217
|
+
* fields in the result object.
|
|
218
|
+
*
|
|
219
|
+
* @param url - Full data URL string.
|
|
220
|
+
* @returns `{ mimeType, data }` — both optional when the regex does not match.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```ts
|
|
224
|
+
* const { mimeType, data } = decodeDataUrl('data:image/png;base64,iVBORw0KGgo=');
|
|
225
|
+
* // mimeType === 'image/png', data === 'iVBORw0KGgo='
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* decodeDataUrl('not-a-data-url'); // { mimeType: undefined, data: undefined }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
80
233
|
function decodeDataUrl(url) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
};
|
|
234
|
+
const matches = (/* @__PURE__ */ new RegExp(/^data:(.*);base64,\s?(.*)$/)).exec(url);
|
|
235
|
+
return {
|
|
236
|
+
mimeType: matches?.[1],
|
|
237
|
+
data: matches?.[2]
|
|
238
|
+
};
|
|
87
239
|
}
|
|
88
|
-
|
|
89
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Returns `true` if `url` starts with `https://` or `http://` (case-sensitive, as in
|
|
242
|
+
* `String#startsWith`). Does not validate that the rest is a well-formed URL — only the scheme
|
|
243
|
+
* prefix. `blob:`, `data:`, and relative paths return `false`.
|
|
244
|
+
*
|
|
245
|
+
* @param url - String to test (often a user-provided or configured href).
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```ts
|
|
249
|
+
* isHttpUrl('https://example.com/path'); // true
|
|
250
|
+
* isHttpUrl('http://localhost:3000'); // true
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```ts
|
|
255
|
+
* isHttpUrl('//cdn.example.com'); // false — no explicit http(s) prefix
|
|
256
|
+
* isHttpUrl('/assets/logo.png'); // false — relative path
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
var isHttpUrl = (url) => {
|
|
260
|
+
return url.startsWith("https://") || url.startsWith("http://");
|
|
90
261
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Returns `true` when `str` is a data URL that {@link decodeDataUrl} can parse **and** the MIME
|
|
264
|
+
* type starts with `image/` (e.g. `image/png`, `image/jpeg`). Requires both a non-empty Base64
|
|
265
|
+
* payload and an image MIME — arbitrary `data:text/plain;base64,...` is `false`.
|
|
266
|
+
*
|
|
267
|
+
* @param str - Candidate string (often `img.src` or API payload).
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* isBase64Image('data:image/png;base64,iVBORw0KGgo='); // true
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* isBase64Image('https://example.com/x.png'); // false
|
|
277
|
+
* isBase64Image('data:text/plain;base64,SGk='); // false
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
var isBase64Image = (str) => {
|
|
281
|
+
const { mimeType, data } = decodeDataUrl(str);
|
|
282
|
+
return !!(data && mimeType?.startsWith("image/"));
|
|
94
283
|
};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
fileToBlob,
|
|
100
|
-
imageToBlob,
|
|
101
|
-
isBase64Image,
|
|
102
|
-
isHttpUrl,
|
|
103
|
-
renderImage,
|
|
104
|
-
rotateImage
|
|
105
|
-
};
|
|
106
|
-
//# sourceMappingURL=media.js.map
|
|
284
|
+
//#endregion
|
|
285
|
+
export { blobToBase64, blobToUrl, decodeDataUrl, fileToBlob, imageToBlob, isBase64Image, isHttpUrl, renderImage, rotateImage };
|
|
286
|
+
|
|
287
|
+
//# sourceMappingURL=media.js.map
|
package/media.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.js","sources":["../src/media.ts"],"sourcesContent":["import { degToRad } from 'yummies/math';\n\nexport function blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\nexport const blobToUrl = (urlOrBlob: string | Blob) =>\n urlOrBlob instanceof Blob ? URL.createObjectURL(urlOrBlob) : urlOrBlob;\n\nexport const fileToBlob = (file: File) => {\n return new Blob([file], { type: file.type });\n};\n\nexport const imageToBlob = (\n imageElement: HTMLImageElement,\n mimeType: string = 'image/png',\n) => {\n const canvas = document.createElement('canvas');\n\n canvas.width = imageElement.naturalWidth || 300;\n canvas.height = imageElement.naturalHeight || 300;\n\n canvas.getContext('2d')!.drawImage(imageElement, 0, 0);\n\n const dataUri = canvas.toDataURL(mimeType, 1);\n const base64data = dataUri.split(',')[1];\n const base64MimeType = dataUri.split(';')[0].slice(5);\n\n const bytes = globalThis.atob(base64data);\n const buf = new ArrayBuffer(bytes.length);\n const array = new Uint8Array(buf);\n\n for (let index = 0; index < bytes.length; index++) {\n array[index] = bytes.charCodeAt(index);\n }\n\n const blob = new Blob([array], { type: base64MimeType });\n\n return blob;\n};\n\n/**\n * Loads and renders an image using `Image`.\n *\n * @returns {Promise<HTMLImageElement>}\n */\nexport const renderImage = (urlOrBlob: Blob | string) =>\n new Promise<HTMLImageElement>((resolve, reject) => {\n const image = new Image();\n image.src = blobToUrl(urlOrBlob);\n image.onload = () => resolve(image);\n image.onerror = () => reject();\n });\n\nfunction cropImageFromCanvas(context: CanvasRenderingContext2D) {\n const canvas = context.canvas;\n let w = canvas.width;\n let h = canvas.height;\n const pix: { x: number[]; y: number[] } = { x: [], y: [] };\n const imageData = context.getImageData(0, 0, canvas.width, canvas.height);\n let x: number;\n let y: number;\n let index: number;\n\n for (y = 0; y < h; y++) {\n for (x = 0; x < w; x++) {\n index = (y * w + x) * 4;\n if (imageData.data[index + 3] > 0) {\n pix.x.push(x);\n pix.y.push(y);\n }\n }\n }\n pix.x.sort((a, b) => a - b);\n pix.y.sort((a, b) => a - b);\n const n = pix.x.length - 1;\n\n w = 1 + pix.x[n] - pix.x[0];\n h = 1 + pix.y[n] - pix.y[0];\n const cut = context.getImageData(pix.x[0], pix.y[0], w, h);\n\n canvas.width = w;\n canvas.height = h;\n context.putImageData(cut, 0, 0);\n return canvas;\n}\n\n// TODO: ломает iphone с огромными изображениями\nexport const rotateImage = (image: HTMLImageElement, angle: number) => {\n const maxSize = Math.max(image.width, image.height);\n const canvas = document.createElement('canvas');\n canvas.width = maxSize;\n canvas.height = maxSize;\n const context = canvas.getContext('2d')!;\n context.save();\n context.translate(canvas.width / 2, canvas.height / 2);\n context.rotate(degToRad(angle));\n context.drawImage(image, -image.width / 2, -image.height / 2);\n context.restore();\n cropImageFromCanvas(context);\n return renderImage(canvas.toDataURL('image/png'));\n};\n\ninterface DecodedDataUrl {\n mimeType?: string;\n data?: string;\n}\n\n/*\n * Returning object which contains base64 data and mime type of passed data url string.\n * */\nexport function decodeDataUrl(url: string): DecodedDataUrl {\n const regex = /^data:(.*);base64,\\s?(.*)$/;\n const matches = new RegExp(regex).exec(url);\n\n return {\n mimeType: matches?.[1],\n data: matches?.[2],\n };\n}\n\nexport const isHttpUrl = (url: string): boolean => {\n return url.startsWith('https://') || url.startsWith('http://');\n};\n\nexport const isBase64Image = (str: string): boolean => {\n const { mimeType, data } = decodeDataUrl(str);\n return !!(data && mimeType?.startsWith('image/'));\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"media.js","names":[],"sources":["../src/media.ts"],"sourcesContent":["import { degToRad } from 'yummies/math';\n\n/**\n * Reads a {@link Blob} as a **data URL** string (`data:<mime>;base64,...`) using {@link FileReader#readAsDataURL}.\n *\n * Useful for previewing uploads, embedding small assets inline, or serializing binary for APIs that\n * expect Base64-in-JSON. The result includes the MIME prefix, not raw Base64 alone — use\n * {@link decodeDataUrl} if you need the payload and type separately.\n *\n * @param blob - Any `Blob` or `File` (files are blobs).\n * @returns Resolves to the data URL string; rejects if reading fails.\n *\n * @example\n * ```ts\n * const dataUrl = await blobToBase64(file);\n * previewImg.src = dataUrl;\n * ```\n *\n * @example\n * ```ts\n * const fromFetch = await fetch('/api/export').then((r) => r.blob());\n * const dataUrl = await blobToBase64(fromFetch);\n * ```\n */\nexport function blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\n/**\n * If `urlOrBlob` is already a string, returns it unchanged. If it is a {@link Blob}, returns\n * `URL.createObjectURL(blob)` — a short-lived `blob:` URL valid in this document until\n * {@link URL.revokeObjectURL} is called.\n *\n * Pair with {@link renderImage} or `<img src>` without re-fetching binary data. **Remember to\n * `revokeObjectURL`** when the URL is no longer needed to avoid retaining blob memory.\n *\n * @param urlOrBlob - Remote/http(s) URL string, data URL, or a `Blob` / `File`.\n * @returns A string suitable for `HTMLImageElement.src` or similar.\n *\n * @example\n * ```ts\n * const src = blobToUrl(uploadedFile);\n * img.src = src;\n * // when done:\n * URL.revokeObjectURL(src);\n * ```\n *\n * @example\n * ```ts\n * blobToUrl('https://cdn.example.com/logo.png'); // passed through as-is\n * ```\n */\nexport const blobToUrl = (urlOrBlob: string | Blob) =>\n urlOrBlob instanceof Blob ? URL.createObjectURL(urlOrBlob) : urlOrBlob;\n\n/**\n * Creates a new {@link Blob} from a {@link File}, copying the bytes and keeping `file.type` as\n * the blob’s MIME type. Handy when an API accepts `Blob` but you only have `File`, or you want a\n * plain blob without the `File` name/lastModified metadata.\n *\n * @param file - Source file from an `<input type=\"file\">` or drag-and-drop.\n * @returns A `Blob` with the same content and `type` as the file.\n *\n * @example\n * ```ts\n * const blob = fileToBlob(fileFromInput);\n * await uploadEndpoint(blob);\n * ```\n *\n * @example\n * ```ts\n * const blob = fileToBlob(imageFile);\n * const url = URL.createObjectURL(blob);\n * ```\n */\nexport const fileToBlob = (file: File) => {\n return new Blob([file], { type: file.type });\n};\n\n/**\n * Draws an {@link HTMLImageElement} onto an offscreen canvas, then builds a {@link Blob} from the\n * raster (via `canvas.toDataURL` + binary decode). Dimensions use `naturalWidth` / `naturalHeight`,\n * falling back to `300×300` if those are zero (e.g. not yet decoded).\n *\n * **CORS:** if the image is cross-origin without proper CORS headers, the canvas may be\n * *tainted* and `toDataURL` can throw — same browser rules as any canvas export.\n *\n * @param imageElement - Loaded image (`complete` / decoded recommended).\n * @param mimeType - Output MIME type, e.g. `'image/png'` (default) or `'image/jpeg'`. Encoder support is browser-dependent.\n * @returns Encoded image as a `Blob` with a matching `type` when possible.\n *\n * @example\n * ```ts\n * const img = await renderImage('/photo.jpg');\n * const jpegBlob = imageToBlob(img, 'image/jpeg');\n * ```\n *\n * @example\n * ```ts\n * const pngBlob = imageToBlob(cachedImg); // default image/png\n * ```\n */\nexport const imageToBlob = (\n imageElement: HTMLImageElement,\n mimeType: string = 'image/png',\n) => {\n const canvas = document.createElement('canvas');\n\n canvas.width = imageElement.naturalWidth || 300;\n canvas.height = imageElement.naturalHeight || 300;\n\n canvas.getContext('2d')!.drawImage(imageElement, 0, 0);\n\n const dataUri = canvas.toDataURL(mimeType, 1);\n const base64data = dataUri.split(',')[1];\n const base64MimeType = dataUri.split(';')[0].slice(5);\n\n const bytes = globalThis.atob(base64data);\n const buf = new ArrayBuffer(bytes.length);\n const array = new Uint8Array(buf);\n\n for (let index = 0; index < bytes.length; index++) {\n array[index] = bytes.charCodeAt(index);\n }\n\n const blob = new Blob([array], { type: base64MimeType });\n\n return blob;\n};\n\n/**\n * Loads a resource into a new `HTMLImageElement`: `src` is set via {@link blobToUrl} (so blobs get\n * object URLs, strings are used directly). Resolves on `load` with the same element; rejects on\n * `error` (e.g. bad URL, network failure, corrupt image) **with no rejection value**.\n *\n * Does not add the node to the DOM — use the returned element for canvas, measuring, or\n * {@link imageToBlob}.\n *\n * @param urlOrBlob - Remote URL, data URL, or `Blob` / `File`.\n * @returns Promise that fulfills with the loaded `HTMLImageElement`.\n *\n * @example\n * ```ts\n * const img = await renderImage('https://example.com/pic.png');\n * document.body.appendChild(img);\n * ```\n *\n * @example\n * ```ts\n * const img = await renderImage(pickedFile);\n * const blob = imageToBlob(img, 'image/webp');\n * ```\n */\nexport const renderImage = (urlOrBlob: Blob | string) =>\n new Promise<HTMLImageElement>((resolve, reject) => {\n const image = new Image();\n image.src = blobToUrl(urlOrBlob);\n image.onload = () => resolve(image);\n image.onerror = () => reject();\n });\n\nfunction cropImageFromCanvas(context: CanvasRenderingContext2D) {\n const canvas = context.canvas;\n let w = canvas.width;\n let h = canvas.height;\n const pix: { x: number[]; y: number[] } = { x: [], y: [] };\n const imageData = context.getImageData(0, 0, canvas.width, canvas.height);\n let x: number;\n let y: number;\n let index: number;\n\n for (y = 0; y < h; y++) {\n for (x = 0; x < w; x++) {\n index = (y * w + x) * 4;\n if (imageData.data[index + 3] > 0) {\n pix.x.push(x);\n pix.y.push(y);\n }\n }\n }\n pix.x.sort((a, b) => a - b);\n pix.y.sort((a, b) => a - b);\n const n = pix.x.length - 1;\n\n w = 1 + pix.x[n] - pix.x[0];\n h = 1 + pix.y[n] - pix.y[0];\n const cut = context.getImageData(pix.x[0], pix.y[0], w, h);\n\n canvas.width = w;\n canvas.height = h;\n context.putImageData(cut, 0, 0);\n return canvas;\n}\n\n// TODO: ломает iphone с огромными изображениями\n/**\n * Rotates `image` around its center on a square canvas (side = max(width, height)), then **crops**\n * transparent margins by trimming to the bounding box of pixels with non-zero alpha.\n * Returns a **new** loaded `HTMLImageElement` (PNG data URL under the hood via {@link renderImage}).\n *\n * `angle` is in **degrees**; converted with {@link degToRad} from `yummies/math`.\n *\n * Very large sources can stress memory on some mobile browsers (known iPhone issues — see TODO in source).\n *\n * @param image - Source image (should be decoded; uses `width` / `height`).\n * @param angle - Rotation in degrees (e.g. `90` for quarter turn).\n * @returns Promise of a new `HTMLImageElement` showing the rotated, cropped result.\n *\n * @example\n * ```ts\n * const upright = await rotateImage(landscapeImg, 90);\n * ```\n *\n * @example\n * ```ts\n * const fixed = await rotateImage(await renderImage(file), -15);\n * ```\n */\nexport const rotateImage = (image: HTMLImageElement, angle: number) => {\n const maxSize = Math.max(image.width, image.height);\n const canvas = document.createElement('canvas');\n canvas.width = maxSize;\n canvas.height = maxSize;\n const context = canvas.getContext('2d')!;\n context.save();\n context.translate(canvas.width / 2, canvas.height / 2);\n context.rotate(degToRad(angle));\n context.drawImage(image, -image.width / 2, -image.height / 2);\n context.restore();\n cropImageFromCanvas(context);\n return renderImage(canvas.toDataURL('image/png'));\n};\n\ninterface DecodedDataUrl {\n mimeType?: string;\n data?: string;\n}\n\n/**\n * Parses a `data:` URL of the form `data:<mime>;base64,<payload>` into its MIME type and raw\n * Base64 body (without the `data:...;base64,` prefix). Non-matching strings yield `undefined`\n * fields in the result object.\n *\n * @param url - Full data URL string.\n * @returns `{ mimeType, data }` — both optional when the regex does not match.\n *\n * @example\n * ```ts\n * const { mimeType, data } = decodeDataUrl('data:image/png;base64,iVBORw0KGgo=');\n * // mimeType === 'image/png', data === 'iVBORw0KGgo='\n * ```\n *\n * @example\n * ```ts\n * decodeDataUrl('not-a-data-url'); // { mimeType: undefined, data: undefined }\n * ```\n */\nexport function decodeDataUrl(url: string): DecodedDataUrl {\n const regex = /^data:(.*);base64,\\s?(.*)$/;\n const matches = new RegExp(regex).exec(url);\n\n return {\n mimeType: matches?.[1],\n data: matches?.[2],\n };\n}\n\n/**\n * Returns `true` if `url` starts with `https://` or `http://` (case-sensitive, as in\n * `String#startsWith`). Does not validate that the rest is a well-formed URL — only the scheme\n * prefix. `blob:`, `data:`, and relative paths return `false`.\n *\n * @param url - String to test (often a user-provided or configured href).\n *\n * @example\n * ```ts\n * isHttpUrl('https://example.com/path'); // true\n * isHttpUrl('http://localhost:3000'); // true\n * ```\n *\n * @example\n * ```ts\n * isHttpUrl('//cdn.example.com'); // false — no explicit http(s) prefix\n * isHttpUrl('/assets/logo.png'); // false — relative path\n * ```\n */\nexport const isHttpUrl = (url: string): boolean => {\n return url.startsWith('https://') || url.startsWith('http://');\n};\n\n/**\n * Returns `true` when `str` is a data URL that {@link decodeDataUrl} can parse **and** the MIME\n * type starts with `image/` (e.g. `image/png`, `image/jpeg`). Requires both a non-empty Base64\n * payload and an image MIME — arbitrary `data:text/plain;base64,...` is `false`.\n *\n * @param str - Candidate string (often `img.src` or API payload).\n *\n * @example\n * ```ts\n * isBase64Image('data:image/png;base64,iVBORw0KGgo='); // true\n * ```\n *\n * @example\n * ```ts\n * isBase64Image('https://example.com/x.png'); // false\n * isBase64Image('data:text/plain;base64,SGk='); // false\n * ```\n */\nexport const isBase64Image = (str: string): boolean => {\n const { mimeType, data } = decodeDataUrl(str);\n return !!(data && mimeType?.startsWith('image/'));\n};\n"],"mappings":"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"}
|