react-native-fast-image 10.1.1 → 10.3.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/README.md +107 -14
- package/android/src/main/java/com/fastimage/FastImageBlur.java +3 -2
- package/android/src/main/java/com/fastimage/FastImageThumbHash.java +246 -0
- package/android/src/main/java/com/fastimage/FastImageViewConverter.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +20 -5
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +125 -48
- package/dist/index.cjs.js +47 -9
- package/dist/index.cjs.js.flow +11 -0
- package/dist/index.cjs.web.js +67 -14
- package/dist/index.cjs.web.js.flow +11 -0
- package/dist/index.d.ts +77 -13
- package/dist/index.js +48 -10
- package/dist/index.js.flow +11 -0
- package/dist/index.web.js +68 -15
- package/dist/index.web.js.flow +11 -0
- package/dist/placeholders.d.ts +4 -0
- package/dist/specs/FastImageViewNativeComponent.d.ts +2 -1
- package/dist/thumbhash.cjs.js +241 -0
- package/dist/thumbhash.d.ts +12 -0
- package/dist/thumbhash.js +240 -0
- package/ios/FastImage/FFFBoxBlur.h +7 -0
- package/ios/FastImage/FFFBoxBlur.m +29 -0
- package/ios/FastImage/FFFThumbHash.h +24 -0
- package/ios/FastImage/FFFThumbHash.m +269 -0
- package/ios/FastImage/FFFastImageView.h +7 -1
- package/ios/FastImage/FFFastImageView.m +93 -46
- package/ios/FastImage/FFFastImageViewComponentView.mm +11 -4
- package/package.json +4 -2
- package/src/index.js.flow +11 -0
- package/src/index.tsx +166 -31
- package/src/index.web.tsx +166 -33
- package/src/placeholders.ts +45 -0
- package/src/specs/FastImageViewNativeComponent.ts +8 -2
- package/src/thumbhash.ts +363 -0
- package/thumbhash/package.json +8 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Placeholders given as strings (`placeholder`, and a source's `blurDataUri`)
|
|
2
|
+
// are data uris, shared by the native (index.tsx) and web (index.web.tsx)
|
|
3
|
+
// versions, or for `placeholder`, the name of an image bundled with the app.
|
|
4
|
+
// They're decoded at once, so they show in the view's first frame; a url
|
|
5
|
+
// would load like an image (and on iOS and Android, not at all), so it's
|
|
6
|
+
// ignored, with a warning in development.
|
|
7
|
+
import type { SourcePlaceholder } from './index'
|
|
8
|
+
import { warnOnce } from './warnings'
|
|
9
|
+
|
|
10
|
+
// Metro's (and React Native's Jest preset's) development flag; other web
|
|
11
|
+
// bundlers may not define it.
|
|
12
|
+
declare const __DEV__: boolean | undefined
|
|
13
|
+
|
|
14
|
+
// Whether `placeholder` is the name of an image bundled with the app (an iOS
|
|
15
|
+
// asset catalog image, an Android drawable): no scheme, not a path.
|
|
16
|
+
export function isImageName(value: string): boolean {
|
|
17
|
+
return !/^[a-z][a-z\d+.-]*:/i.test(value) && !value.startsWith('/')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Whether a source has a thumbhash or blurDataUri, or `placeholder` is one
|
|
21
|
+
// (for every size of the source) rather than an image.
|
|
22
|
+
export function hasSourcePlaceholder(
|
|
23
|
+
value: unknown,
|
|
24
|
+
): value is SourcePlaceholder {
|
|
25
|
+
return (
|
|
26
|
+
!!value &&
|
|
27
|
+
typeof value === 'object' &&
|
|
28
|
+
('thumbhash' in value || 'blurDataUri' in value)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The value if it's a data uri, otherwise undefined (with a warning for
|
|
33
|
+
// another string).
|
|
34
|
+
export function dataUri(value: unknown): string | undefined {
|
|
35
|
+
if (typeof value !== 'string' || value === '') return undefined
|
|
36
|
+
if (/^data:/i.test(value)) return value
|
|
37
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
38
|
+
warnOnce(
|
|
39
|
+
'placeholder-url',
|
|
40
|
+
`react-native-fast-image: \`placeholder\` and \`blurDataUri\` don't load urls: "${value.slice(0, 60)}" is ignored. Use a data uri (data:image/…), or for \`placeholder\`, a require()d image or the name of an image bundled with the app.`,
|
|
41
|
+
'warn',
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
@@ -45,8 +45,14 @@ export interface NativeProps extends ViewProps {
|
|
|
45
45
|
source?: CodegenTypes.UnsafeMixed
|
|
46
46
|
// Several sources (sizes of one image), picked from for the view's size.
|
|
47
47
|
sources?: CodegenTypes.UnsafeMixed
|
|
48
|
-
//
|
|
49
|
-
|
|
48
|
+
// The `placeholder` prop: a resolved asset source ({ uri, width, height,
|
|
49
|
+
// scale }) or a data uri ({ uri } on iOS; the uri on Android).
|
|
50
|
+
placeholder?: CodegenTypes.UnsafeMixed
|
|
51
|
+
// What shows in place of placeholder while a source loads: its ThumbHash,
|
|
52
|
+
// or its blurDataUri (blurred natively), by the source's index ('' for
|
|
53
|
+
// none; one for a single source). Apart from the sources, so a new one
|
|
54
|
+
// for the same image doesn't load it again.
|
|
55
|
+
sourcePlaceholders?: ReadonlyArray<string>
|
|
50
56
|
// objectFit, resolved (fill, contain, cover, none or scale-down).
|
|
51
57
|
objectFit?: CodegenTypes.WithDefault<string, 'cover'>
|
|
52
58
|
// objectPosition, as CSS computes it: on each axis the image's left or top
|
package/src/thumbhash.ts
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// ThumbHashes (https://evanw.github.io/thumbhash/) on the web, as
|
|
2
|
+
// `react-native-fast-image/thumbhash`: importing it makes the web version
|
|
3
|
+
// show sources' thumbhashes (iOS and Android decode them natively), and
|
|
4
|
+
// thumbHashToBlurDataUri makes a source's `blurDataUri` from one, e.g. on a
|
|
5
|
+
// server, so a page shows it without any JavaScript. FastImage doesn't import
|
|
6
|
+
// it, so web bundles only have it if the app does.
|
|
7
|
+
//
|
|
8
|
+
// The data uri is a PNG of at most 8 × 8 pixels: a ThumbHash holds at most 7
|
|
9
|
+
// frequencies on its longer side, so more pixels add bytes, not detail. The
|
|
10
|
+
// web version blurs it as it scales it up, as it does every blurDataUri.
|
|
11
|
+
//
|
|
12
|
+
// thumbHashToRGBA and rgbaToDataURL are Evan Wallace's reference code
|
|
13
|
+
// (https://github.com/evanw/thumbhash, js/thumbhash.js), typed, with a check
|
|
14
|
+
// that the hash is long enough for what its header says it holds:
|
|
15
|
+
//
|
|
16
|
+
// Copyright (c) 2023 Evan Wallace
|
|
17
|
+
//
|
|
18
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
19
|
+
// of this software and associated documentation files (the "Software"), to
|
|
20
|
+
// deal in the Software without restriction, including without limitation the
|
|
21
|
+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
22
|
+
// sell copies of the Software, and to permit persons to whom the Software is
|
|
23
|
+
// furnished to do so, subject to the following conditions:
|
|
24
|
+
//
|
|
25
|
+
// The above copyright notice and this permission notice shall be included in
|
|
26
|
+
// all copies or substantial portions of the Software.
|
|
27
|
+
//
|
|
28
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
29
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
30
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
31
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
32
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
33
|
+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
34
|
+
// IN THE SOFTWARE.
|
|
35
|
+
|
|
36
|
+
// Recently made data uris, by their hash, the least recently used dropped.
|
|
37
|
+
const cache = new Map<string, string | undefined>()
|
|
38
|
+
const CACHE_SIZE = 256
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Makes a source's `blurDataUri` from a ThumbHash, e.g. on your server, so
|
|
42
|
+
* the page shows the preview without any JavaScript. Undefined for an invalid
|
|
43
|
+
* hash. Works in browsers, Node and React Native.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```js
|
|
47
|
+
* // e.g. on your server, so the page shows the preview without any JavaScript
|
|
48
|
+
* const source = { uri: photo.url, blurDataUri: thumbHashToBlurDataUri(photo.thumbhash) }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function thumbHashToBlurDataUri(
|
|
52
|
+
thumbhash: string | undefined,
|
|
53
|
+
): string | undefined {
|
|
54
|
+
if (!thumbhash) return undefined
|
|
55
|
+
if (cache.has(thumbhash)) {
|
|
56
|
+
const uri = cache.get(thumbhash)
|
|
57
|
+
cache.delete(thumbhash)
|
|
58
|
+
cache.set(thumbhash, uri)
|
|
59
|
+
return uri
|
|
60
|
+
}
|
|
61
|
+
const hash = base64Bytes(thumbhash)
|
|
62
|
+
const image = hash ? thumbHashToRGBA(hash) : undefined
|
|
63
|
+
const small = image ? shrink(image.w, image.h, image.rgba, 8) : undefined
|
|
64
|
+
const uri = small ? rgbaToDataURL(small.w, small.h, small.rgba) : undefined
|
|
65
|
+
if (cache.size >= CACHE_SIZE) {
|
|
66
|
+
cache.delete(cache.keys().next().value as string)
|
|
67
|
+
}
|
|
68
|
+
cache.set(thumbhash, uri)
|
|
69
|
+
return uri
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The web version's decoder, which index.web.tsx finds here without importing
|
|
73
|
+
// this module (the same key from any copy of the package).
|
|
74
|
+
;(globalThis as Record<symbol, unknown>)[
|
|
75
|
+
Symbol.for('react-native-fast-image/thumbhash')
|
|
76
|
+
] = thumbHashToBlurDataUri
|
|
77
|
+
|
|
78
|
+
// The pixels averaged down so the longer side is at most max.
|
|
79
|
+
function shrink(w: number, h: number, rgba: Uint8Array, max: number) {
|
|
80
|
+
const scale = Math.max(w, h) / max
|
|
81
|
+
if (scale <= 1) return { w, h, rgba }
|
|
82
|
+
const sw = Math.max(1, Math.round(w / scale))
|
|
83
|
+
const sh = Math.max(1, Math.round(h / scale))
|
|
84
|
+
const out = new Uint8Array(sw * sh * 4)
|
|
85
|
+
for (let y = 0; y < sh; y++) {
|
|
86
|
+
const y0 = Math.floor((y * h) / sh)
|
|
87
|
+
const y1 = Math.floor(((y + 1) * h) / sh)
|
|
88
|
+
for (let x = 0; x < sw; x++) {
|
|
89
|
+
const x0 = Math.floor((x * w) / sw)
|
|
90
|
+
const x1 = Math.floor(((x + 1) * w) / sw)
|
|
91
|
+
for (let c = 0; c < 4; c++) {
|
|
92
|
+
let sum = 0
|
|
93
|
+
for (let yy = y0; yy < y1; yy++)
|
|
94
|
+
for (let xx = x0; xx < x1; xx++)
|
|
95
|
+
sum += rgba[(yy * w + xx) * 4 + c]
|
|
96
|
+
out[(y * sw + x) * 4 + c] = Math.round(
|
|
97
|
+
sum / ((y1 - y0) * (x1 - x0)),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { w: sw, h: sh, rgba: out }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A ThumbHash's bytes: base64, standard or url-safe, with or without
|
|
106
|
+
// padding.
|
|
107
|
+
function base64Bytes(base64: string): Uint8Array | undefined {
|
|
108
|
+
const standard = base64.trim().replace(/-/g, '+').replace(/_/g, '/')
|
|
109
|
+
if (!/^[A-Za-z0-9+/]*=*$/.test(standard)) return undefined
|
|
110
|
+
try {
|
|
111
|
+
const binary = atob(
|
|
112
|
+
standard.replace(/=+$/, '') +
|
|
113
|
+
'='.repeat((4 - (standard.replace(/=+$/, '').length % 4)) % 4),
|
|
114
|
+
)
|
|
115
|
+
return Uint8Array.from(binary, (c) => c.charCodeAt(0))
|
|
116
|
+
} catch {
|
|
117
|
+
return undefined
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// How many AC factors a channel of nx × ny has.
|
|
122
|
+
function count(nx: number, ny: number) {
|
|
123
|
+
let n = 0
|
|
124
|
+
for (let cy = 0; cy < ny; cy++)
|
|
125
|
+
for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++) n++
|
|
126
|
+
return n
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function thumbHashToRGBA(
|
|
130
|
+
hash: Uint8Array,
|
|
131
|
+
): { w: number; h: number; rgba: Uint8Array } | undefined {
|
|
132
|
+
const { PI, min, max, cos, round } = Math
|
|
133
|
+
if (hash.length < 5) return undefined
|
|
134
|
+
|
|
135
|
+
// Read the constants
|
|
136
|
+
const header24 = hash[0] | (hash[1] << 8) | (hash[2] << 16)
|
|
137
|
+
const header16 = hash[3] | (hash[4] << 8)
|
|
138
|
+
const l_dc = (header24 & 63) / 63
|
|
139
|
+
const p_dc = ((header24 >> 6) & 63) / 31.5 - 1
|
|
140
|
+
const q_dc = ((header24 >> 12) & 63) / 31.5 - 1
|
|
141
|
+
const l_scale = ((header24 >> 18) & 31) / 31
|
|
142
|
+
const hasAlpha = header24 >> 23
|
|
143
|
+
const p_scale = ((header16 >> 3) & 63) / 63
|
|
144
|
+
const q_scale = ((header16 >> 9) & 63) / 63
|
|
145
|
+
const isLandscape = header16 >> 15
|
|
146
|
+
const lx = max(3, isLandscape ? (hasAlpha ? 5 : 7) : header16 & 7)
|
|
147
|
+
const ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7)
|
|
148
|
+
const ac_start = hasAlpha ? 6 : 5
|
|
149
|
+
const ac_count =
|
|
150
|
+
count(lx, ly) + 2 * count(3, 3) + (hasAlpha ? count(5, 5) : 0)
|
|
151
|
+
if (hash.length < ac_start + Math.ceil(ac_count / 2)) return undefined
|
|
152
|
+
const a_dc = hasAlpha ? (hash[5] & 15) / 15 : 1
|
|
153
|
+
const a_scale = (hash[5] >> 4) / 15
|
|
154
|
+
|
|
155
|
+
// Read the varying factors (boost saturation by 1.25x to compensate for quantization)
|
|
156
|
+
let ac_index = 0
|
|
157
|
+
const decodeChannel = (nx: number, ny: number, scale: number) => {
|
|
158
|
+
const ac: number[] = []
|
|
159
|
+
for (let cy = 0; cy < ny; cy++)
|
|
160
|
+
for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++)
|
|
161
|
+
ac.push(
|
|
162
|
+
(((hash[ac_start + (ac_index >> 1)] >>
|
|
163
|
+
((ac_index++ & 1) << 2)) &
|
|
164
|
+
15) /
|
|
165
|
+
7.5 -
|
|
166
|
+
1) *
|
|
167
|
+
scale,
|
|
168
|
+
)
|
|
169
|
+
return ac
|
|
170
|
+
}
|
|
171
|
+
const l_ac = decodeChannel(lx, ly, l_scale)
|
|
172
|
+
const p_ac = decodeChannel(3, 3, p_scale * 1.25)
|
|
173
|
+
const q_ac = decodeChannel(3, 3, q_scale * 1.25)
|
|
174
|
+
const a_ac = hasAlpha ? decodeChannel(5, 5, a_scale) : []
|
|
175
|
+
|
|
176
|
+
// Decode using the DCT into RGB
|
|
177
|
+
const ratio = thumbHashToApproximateAspectRatio(hash)
|
|
178
|
+
const w = max(1, round(ratio > 1 ? 32 : 32 * ratio))
|
|
179
|
+
const h = max(1, round(ratio > 1 ? 32 / ratio : 32))
|
|
180
|
+
const rgba = new Uint8Array(w * h * 4)
|
|
181
|
+
const fx: number[] = []
|
|
182
|
+
const fy: number[] = []
|
|
183
|
+
for (let y = 0, i = 0; y < h; y++) {
|
|
184
|
+
for (let x = 0; x < w; x++, i += 4) {
|
|
185
|
+
let l = l_dc,
|
|
186
|
+
p = p_dc,
|
|
187
|
+
q = q_dc,
|
|
188
|
+
a = a_dc
|
|
189
|
+
|
|
190
|
+
// Precompute the coefficients
|
|
191
|
+
for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++)
|
|
192
|
+
fx[cx] = cos((PI / w) * (x + 0.5) * cx)
|
|
193
|
+
for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++)
|
|
194
|
+
fy[cy] = cos((PI / h) * (y + 0.5) * cy)
|
|
195
|
+
|
|
196
|
+
// Decode L
|
|
197
|
+
for (let cy = 0, j = 0; cy < ly; cy++)
|
|
198
|
+
for (
|
|
199
|
+
let cx = cy ? 0 : 1, fy2 = fy[cy] * 2;
|
|
200
|
+
cx * ly < lx * (ly - cy);
|
|
201
|
+
cx++, j++
|
|
202
|
+
)
|
|
203
|
+
l += l_ac[j] * fx[cx] * fy2
|
|
204
|
+
|
|
205
|
+
// Decode P and Q
|
|
206
|
+
for (let cy = 0, j = 0; cy < 3; cy++) {
|
|
207
|
+
for (
|
|
208
|
+
let cx = cy ? 0 : 1, fy2 = fy[cy] * 2;
|
|
209
|
+
cx < 3 - cy;
|
|
210
|
+
cx++, j++
|
|
211
|
+
) {
|
|
212
|
+
const f = fx[cx] * fy2
|
|
213
|
+
p += p_ac[j] * f
|
|
214
|
+
q += q_ac[j] * f
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Decode A
|
|
219
|
+
if (hasAlpha)
|
|
220
|
+
for (let cy = 0, j = 0; cy < 5; cy++)
|
|
221
|
+
for (
|
|
222
|
+
let cx = cy ? 0 : 1, fy2 = fy[cy] * 2;
|
|
223
|
+
cx < 5 - cy;
|
|
224
|
+
cx++, j++
|
|
225
|
+
)
|
|
226
|
+
a += a_ac[j] * fx[cx] * fy2
|
|
227
|
+
|
|
228
|
+
// Convert to RGB
|
|
229
|
+
const b = l - (2 / 3) * p
|
|
230
|
+
const r = (3 * l - b + q) / 2
|
|
231
|
+
const g = r - q
|
|
232
|
+
rgba[i] = max(0, 255 * min(1, r))
|
|
233
|
+
rgba[i + 1] = max(0, 255 * min(1, g))
|
|
234
|
+
rgba[i + 2] = max(0, 255 * min(1, b))
|
|
235
|
+
rgba[i + 3] = max(0, 255 * min(1, a))
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { w, h, rgba }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function thumbHashToApproximateAspectRatio(hash: Uint8Array) {
|
|
242
|
+
const header = hash[3]
|
|
243
|
+
const hasAlpha = hash[2] & 0x80
|
|
244
|
+
const isLandscape = hash[4] & 0x80
|
|
245
|
+
const lx = isLandscape ? (hasAlpha ? 5 : 7) : header & 7
|
|
246
|
+
const ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7
|
|
247
|
+
return ly ? lx / ly : 1
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// A PNG (uncompressed) of the pixels, as a data uri.
|
|
251
|
+
function rgbaToDataURL(w: number, h: number, rgba: Uint8Array) {
|
|
252
|
+
const row = w * 4 + 1
|
|
253
|
+
const idat = 6 + h * (5 + row)
|
|
254
|
+
const bytes = [
|
|
255
|
+
137,
|
|
256
|
+
80,
|
|
257
|
+
78,
|
|
258
|
+
71,
|
|
259
|
+
13,
|
|
260
|
+
10,
|
|
261
|
+
26,
|
|
262
|
+
10,
|
|
263
|
+
0,
|
|
264
|
+
0,
|
|
265
|
+
0,
|
|
266
|
+
13,
|
|
267
|
+
73,
|
|
268
|
+
72,
|
|
269
|
+
68,
|
|
270
|
+
82,
|
|
271
|
+
0,
|
|
272
|
+
0,
|
|
273
|
+
w >> 8,
|
|
274
|
+
w & 255,
|
|
275
|
+
0,
|
|
276
|
+
0,
|
|
277
|
+
h >> 8,
|
|
278
|
+
h & 255,
|
|
279
|
+
8,
|
|
280
|
+
6,
|
|
281
|
+
0,
|
|
282
|
+
0,
|
|
283
|
+
0,
|
|
284
|
+
0,
|
|
285
|
+
0,
|
|
286
|
+
0,
|
|
287
|
+
0,
|
|
288
|
+
idat >>> 24,
|
|
289
|
+
(idat >> 16) & 255,
|
|
290
|
+
(idat >> 8) & 255,
|
|
291
|
+
idat & 255,
|
|
292
|
+
73,
|
|
293
|
+
68,
|
|
294
|
+
65,
|
|
295
|
+
84,
|
|
296
|
+
120,
|
|
297
|
+
1,
|
|
298
|
+
]
|
|
299
|
+
const table = [
|
|
300
|
+
0, 498536548, 997073096, 651767980, 1994146192, 1802195444, 1303535960,
|
|
301
|
+
1342533948, -306674912, -267414716, -690576408, -882789492, -1687895376,
|
|
302
|
+
-2032938284, -1609899400, -1111625188,
|
|
303
|
+
]
|
|
304
|
+
let a = 1,
|
|
305
|
+
b = 0
|
|
306
|
+
for (let y = 0, i = 0, end = row - 1; y < h; y++, end += row - 1) {
|
|
307
|
+
bytes.push(
|
|
308
|
+
y + 1 < h ? 0 : 1,
|
|
309
|
+
row & 255,
|
|
310
|
+
row >> 8,
|
|
311
|
+
~row & 255,
|
|
312
|
+
(row >> 8) ^ 255,
|
|
313
|
+
0,
|
|
314
|
+
)
|
|
315
|
+
for (b = (b + a) % 65521; i < end; i++) {
|
|
316
|
+
const u = rgba[i] & 255
|
|
317
|
+
bytes.push(u)
|
|
318
|
+
a = (a + u) % 65521
|
|
319
|
+
b = (b + a) % 65521
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
bytes.push(
|
|
323
|
+
b >> 8,
|
|
324
|
+
b & 255,
|
|
325
|
+
a >> 8,
|
|
326
|
+
a & 255,
|
|
327
|
+
0,
|
|
328
|
+
0,
|
|
329
|
+
0,
|
|
330
|
+
0,
|
|
331
|
+
0,
|
|
332
|
+
0,
|
|
333
|
+
0,
|
|
334
|
+
0,
|
|
335
|
+
73,
|
|
336
|
+
69,
|
|
337
|
+
78,
|
|
338
|
+
68,
|
|
339
|
+
174,
|
|
340
|
+
66,
|
|
341
|
+
96,
|
|
342
|
+
130,
|
|
343
|
+
)
|
|
344
|
+
for (let [start, end] of [
|
|
345
|
+
[12, 29],
|
|
346
|
+
[37, 41 + idat],
|
|
347
|
+
]) {
|
|
348
|
+
let c = ~0
|
|
349
|
+
for (let i = start; i < end; i++) {
|
|
350
|
+
c ^= bytes[i]
|
|
351
|
+
c = (c >>> 4) ^ table[c & 15]
|
|
352
|
+
c = (c >>> 4) ^ table[c & 15]
|
|
353
|
+
}
|
|
354
|
+
c = ~c
|
|
355
|
+
bytes[end++] = c >>> 24
|
|
356
|
+
bytes[end++] = (c >> 16) & 255
|
|
357
|
+
bytes[end++] = (c >> 8) & 255
|
|
358
|
+
bytes[end++] = c & 255
|
|
359
|
+
}
|
|
360
|
+
let binary = ''
|
|
361
|
+
for (const byte of bytes) binary += String.fromCharCode(byte)
|
|
362
|
+
return 'data:image/png;base64,' + btoa(binary)
|
|
363
|
+
}
|