mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import {Image as RNImage} from 'react-native-image-crop-picker'
2
3import {Dimensions} from './types'
4import {blobToDataUri, getDataUriSize} from './util'
5
6export async function compressIfNeeded(
7 img: RNImage,
8 maxSize: number,
9): Promise<RNImage> {
10 if (img.size < maxSize) {
11 return img
12 }
13 return await doResize(img.path, {
14 width: img.width,
15 height: img.height,
16 mode: 'stretch',
17 maxSize,
18 })
19}
20
21export interface DownloadAndResizeOpts {
22 uri: string
23 width: number
24 height: number
25 mode: 'contain' | 'cover' | 'stretch'
26 maxSize: number
27 timeout: number
28}
29
30export async function downloadAndResize(opts: DownloadAndResizeOpts) {
31 const controller = new AbortController()
32 const to = setTimeout(() => controller.abort(), opts.timeout || 5e3)
33 const res = await fetch(opts.uri)
34 const resBody = await res.blob()
35 clearTimeout(to)
36
37 const dataUri = await blobToDataUri(resBody)
38 return await doResize(dataUri, opts)
39}
40
41export async function shareImageModal(_opts: {uri: string}) {
42 // TODO
43 throw new Error('TODO')
44}
45
46export async function saveImageToAlbum(_opts: {uri: string; album: string}) {
47 // TODO
48 throw new Error('TODO')
49}
50
51export async function getImageDim(path: string): Promise<Dimensions> {
52 var img = document.createElement('img')
53 const promise = new Promise((resolve, reject) => {
54 img.onload = resolve
55 img.onerror = reject
56 })
57 img.src = path
58 await promise
59 return {width: img.width, height: img.height}
60}
61
62// internal methods
63// =
64
65interface DoResizeOpts {
66 width: number
67 height: number
68 mode: 'contain' | 'cover' | 'stretch'
69 maxSize: number
70}
71
72async function doResize(dataUri: string, opts: DoResizeOpts): Promise<RNImage> {
73 let newDataUri
74
75 for (let i = 0; i <= 10; i++) {
76 newDataUri = await createResizedImage(dataUri, {
77 width: opts.width,
78 height: opts.height,
79 quality: 1 - i * 0.1,
80 mode: opts.mode,
81 })
82 if (getDataUriSize(newDataUri) < opts.maxSize) {
83 break
84 }
85 }
86 if (!newDataUri) {
87 throw new Error('Failed to compress image')
88 }
89 return {
90 path: newDataUri,
91 mime: 'image/jpeg',
92 size: getDataUriSize(newDataUri),
93 width: opts.width,
94 height: opts.height,
95 }
96}
97
98function createResizedImage(
99 dataUri: string,
100 {
101 width,
102 height,
103 quality,
104 mode,
105 }: {
106 width: number
107 height: number
108 quality: number
109 mode: 'contain' | 'cover' | 'stretch'
110 },
111): Promise<string> {
112 return new Promise((resolve, reject) => {
113 const img = document.createElement('img')
114 img.addEventListener('load', () => {
115 const canvas = document.createElement('canvas')
116 const ctx = canvas.getContext('2d')
117 if (!ctx) {
118 return reject(new Error('Failed to resize image'))
119 }
120
121 let scale = 1
122 if (mode === 'cover') {
123 scale = img.width < img.height ? width / img.width : height / img.height
124 } else if (mode === 'contain') {
125 scale = img.width > img.height ? width / img.width : height / img.height
126 }
127 let w = img.width * scale
128 let h = img.height * scale
129
130 canvas.width = w
131 canvas.height = h
132
133 ctx.drawImage(img, 0, 0, w, h)
134 resolve(canvas.toDataURL('image/jpeg', quality))
135 })
136 img.addEventListener('error', ev => {
137 reject(ev.error)
138 })
139 img.src = dataUri
140 })
141}
142
143export async function saveBytesToDisk(
144 filename: string,
145 bytes: Uint8Array,
146 type: string,
147) {
148 const blob = new Blob([bytes], {type})
149 const url = URL.createObjectURL(blob)
150 await downloadUrl(url, filename)
151 // Firefox requires a small delay
152 setTimeout(() => URL.revokeObjectURL(url), 100)
153 return true
154}
155
156async function downloadUrl(href: string, filename: string) {
157 const a = document.createElement('a')
158 a.href = href
159 a.download = filename
160 a.click()
161}
162
163export async function safeDeleteAsync() {
164 // no-op
165}