@live-change/image-frontend 0.0.3
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 +21 -0
- package/front/index.html +11 -0
- package/front/public/favicon.ico +0 -0
- package/front/public/images/empty-photo.svg +38 -0
- package/front/public/images/empty-user-photo.svg +33 -0
- package/front/public/images/logo.svg +34 -0
- package/front/public/images/logo128.png +0 -0
- package/front/src/App.vue +32 -0
- package/front/src/EditorTest.vue +28 -0
- package/front/src/Image.vue +137 -0
- package/front/src/ImageCrop.vue +382 -0
- package/front/src/ImageEditor.vue +211 -0
- package/front/src/ImageUpload.js +132 -0
- package/front/src/NavBar.vue +105 -0
- package/front/src/UploadTest.vue +57 -0
- package/front/src/dom.js +82 -0
- package/front/src/entry-client.js +6 -0
- package/front/src/entry-server.js +6 -0
- package/front/src/imageResizer.js +5 -0
- package/front/src/imageUploads.js +29 -0
- package/front/src/imageUtils.js +438 -0
- package/front/src/preprocessImageFile.js +122 -0
- package/front/src/router.js +41 -0
- package/front/vite.config.js +18 -0
- package/index.js +7 -0
- package/package.json +76 -0
- package/server/init.js +5 -0
- package/server/services.config.js +17 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
|
|
2
|
+
function hasAlpha(canvas) {
|
|
3
|
+
const context = canvas.getContext('2d')
|
|
4
|
+
const data = context.getImageData(0, 0, canvas.width, canvas.height).data
|
|
5
|
+
for (let i = 3, n = data.length; i < n; i+=4) {
|
|
6
|
+
if (data[i] < 255) {
|
|
7
|
+
return true
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getOrientation(jpegData) {
|
|
13
|
+
if(window.forceOrientation) return window.forceOrientation
|
|
14
|
+
|
|
15
|
+
let view = new DataView(jpegData)
|
|
16
|
+
if (view.getUint16(0, false) != 0xFFD8) return undefined
|
|
17
|
+
let length = view.byteLength, offset = 2
|
|
18
|
+
while (offset < length) {
|
|
19
|
+
let marker = view.getUint16(offset, false)
|
|
20
|
+
offset += 2
|
|
21
|
+
if (marker == 0xFFE1) {
|
|
22
|
+
if (view.getUint32(offset += 2, false) != 0x45786966) return undefined
|
|
23
|
+
let little = view.getUint16(offset += 6, false) == 0x4949
|
|
24
|
+
offset += view.getUint32(offset + 4, little)
|
|
25
|
+
let tags = view.getUint16(offset, little)
|
|
26
|
+
offset += 2
|
|
27
|
+
for (let i = 0; i < tags; i++)
|
|
28
|
+
if (view.getUint16(offset + (i * 12), little) == 0x0112)
|
|
29
|
+
return (view.getUint16(offset + (i * 12) + 8, little))
|
|
30
|
+
}
|
|
31
|
+
else if ((marker & 0xFF00) != 0xFF00) break;
|
|
32
|
+
else offset += view.getUint16(offset, false)
|
|
33
|
+
}
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function blobToDataUrl(file) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
let reader = new FileReader()
|
|
40
|
+
reader.onload = function(e) {
|
|
41
|
+
resolve(e.target.result)
|
|
42
|
+
}
|
|
43
|
+
reader.onerror = function(e) {
|
|
44
|
+
reject(e.target.error)
|
|
45
|
+
}
|
|
46
|
+
reader.readAsDataURL(file)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadImageUpload(file) {
|
|
51
|
+
return new Promise(function(resolve, reject) {
|
|
52
|
+
let img = document.createElement("img")
|
|
53
|
+
let reader = new FileReader()
|
|
54
|
+
reader.onload = async function(e) {
|
|
55
|
+
if(file.type == "image/jpeg") {
|
|
56
|
+
let headerReader = new FileReader()
|
|
57
|
+
headerReader.onload=function(he) {
|
|
58
|
+
let orientation = getOrientation(he.target.result)
|
|
59
|
+
img.onload = function(e) {
|
|
60
|
+
resolve({
|
|
61
|
+
image: img,
|
|
62
|
+
type: file.type,
|
|
63
|
+
orientation: orientation,
|
|
64
|
+
sizeSwap: orientation>4,
|
|
65
|
+
width: orientation>4 ? img.height : img.width,
|
|
66
|
+
height: orientation>4 ? img.width : img.height
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
img.src = e.target.result
|
|
70
|
+
}
|
|
71
|
+
headerReader.onerror = function(e) {
|
|
72
|
+
reject(e.target.error)
|
|
73
|
+
}
|
|
74
|
+
headerReader.readAsArrayBuffer(file.slice(0, 64 * 1024))
|
|
75
|
+
} else {
|
|
76
|
+
img.onload = function(e) {
|
|
77
|
+
resolve({
|
|
78
|
+
image: img,
|
|
79
|
+
type: file.type,
|
|
80
|
+
width: img.width,
|
|
81
|
+
height: img.height
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
img.src = e.target.result
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
reader.onerror = function(e) {
|
|
88
|
+
reject(e.target.error)
|
|
89
|
+
}
|
|
90
|
+
reader.readAsDataURL(file)
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadImage(url) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
let image = new Image
|
|
97
|
+
image.onload = () => {
|
|
98
|
+
resolve(image)
|
|
99
|
+
}
|
|
100
|
+
image.onerror = (ev) => {
|
|
101
|
+
reject(ev)
|
|
102
|
+
}
|
|
103
|
+
image.src = url
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resize(img, w, h) {
|
|
108
|
+
let canvas=imageUtils.imageToCanvas(img)
|
|
109
|
+
resizeCanvas(canvas,w,h)
|
|
110
|
+
return canvas.toDataURL("image/jpeg",1)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 0 1 2 3 4 5 6 7 8
|
|
114
|
+
const reverseOrientations = [0, 0, 2, 3, 4, 5, 8, 7, 6]
|
|
115
|
+
|
|
116
|
+
let exifOrientationSupportPromise = null
|
|
117
|
+
async function isExifOrientationSupported() {
|
|
118
|
+
if(!exifOrientationSupportPromise) exifOrientationSupportPromise = new Promise((resolve, reject) => {
|
|
119
|
+
const img = new Image()
|
|
120
|
+
img.onerror = function() {
|
|
121
|
+
resolve(false)
|
|
122
|
+
}
|
|
123
|
+
img.onload = function() {
|
|
124
|
+
resolve(img.width !== 2)
|
|
125
|
+
}
|
|
126
|
+
img.src = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4QAiRXhpZgAASUkqAAgAAAABABIBAwABAAAABgASAAAAAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAABAAIDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/iiiigD/2Q==';
|
|
127
|
+
})
|
|
128
|
+
return exifOrientationSupportPromise
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cancelOrientation(canvas, orientation) {
|
|
132
|
+
const change = reverseOrientations[orientation]
|
|
133
|
+
if(change) return changeOrientation(canvas, change)
|
|
134
|
+
return canvas
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function changeOrientation(canvas, orientation) {
|
|
138
|
+
console.log("CHANGE ORIENTATION", orientation, canvas.width, canvas.height, canvas)
|
|
139
|
+
const w = canvas.width
|
|
140
|
+
const h = canvas.height
|
|
141
|
+
let w2 = 0
|
|
142
|
+
let h2 = 0
|
|
143
|
+
const fromIData = canvas.getContext("2d").getImageData(0, 0, w, h)
|
|
144
|
+
const fromData = fromIData.data
|
|
145
|
+
let toIData
|
|
146
|
+
switch(orientation) {
|
|
147
|
+
case 2 : {
|
|
148
|
+
w2=w
|
|
149
|
+
h2=h
|
|
150
|
+
canvas.width = w2
|
|
151
|
+
canvas.height = h2
|
|
152
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
153
|
+
const toData = toIData.data
|
|
154
|
+
for(let y=0; y<h; y++) {
|
|
155
|
+
for(let x=0; x<w; x++) {
|
|
156
|
+
const from=x+y*w
|
|
157
|
+
const to=(w-x-1)+y*w
|
|
158
|
+
toData[(to<<2)+0] = fromData[(from<<2)+0]
|
|
159
|
+
toData[(to<<2)+1] = fromData[(from<<2)+1]
|
|
160
|
+
toData[(to<<2)+2] = fromData[(from<<2)+2]
|
|
161
|
+
toData[(to<<2)+3] = fromData[(from<<2)+3]
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} break;
|
|
165
|
+
case 3 : {
|
|
166
|
+
w2 = w
|
|
167
|
+
h2 = h
|
|
168
|
+
canvas.width = w2
|
|
169
|
+
canvas.height = h2
|
|
170
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
171
|
+
const toData = toIData.data
|
|
172
|
+
for (let y = 0; y < h; y++) {
|
|
173
|
+
for (let x = 0; x < w; x++) {
|
|
174
|
+
const from = (w - x - 1) + y * w
|
|
175
|
+
const to = x + (h - y - 1) * w
|
|
176
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
177
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
178
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
179
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}break;
|
|
183
|
+
case 4 : {
|
|
184
|
+
w2=w
|
|
185
|
+
h2=h
|
|
186
|
+
canvas.width = w2
|
|
187
|
+
canvas.height = h2
|
|
188
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
189
|
+
const toData = toIData.data
|
|
190
|
+
for(let y=0; y<h; y++) {
|
|
191
|
+
for(let x=0; x<w; x++) {
|
|
192
|
+
const from = x+y*w
|
|
193
|
+
const to = x+(h-y-1)*w
|
|
194
|
+
toData[(to<<2)+0] = fromData[(from<<2)+0]
|
|
195
|
+
toData[(to<<2)+1] = fromData[(from<<2)+1]
|
|
196
|
+
toData[(to<<2)+2] = fromData[(from<<2)+2]
|
|
197
|
+
toData[(to<<2)+3] = fromData[(from<<2)+3]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} break;
|
|
201
|
+
case 5 : {
|
|
202
|
+
w2 = h
|
|
203
|
+
h2 = w
|
|
204
|
+
canvas.width = w2
|
|
205
|
+
canvas.height = h2
|
|
206
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
207
|
+
const toData = toIData.data
|
|
208
|
+
for (let y = 0; y < h; y++) {
|
|
209
|
+
for (let x = 0; x < w; x++) {
|
|
210
|
+
const from = x + y * w
|
|
211
|
+
const to = y + x * w2
|
|
212
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
213
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
214
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
215
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} break;
|
|
219
|
+
case 6 : {
|
|
220
|
+
w2 = h
|
|
221
|
+
h2 = w
|
|
222
|
+
canvas.width = w2
|
|
223
|
+
canvas.height = h2
|
|
224
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
225
|
+
const toData = toIData.data
|
|
226
|
+
for (let y = 0; y < h; y++) {
|
|
227
|
+
for (let x = 0; x < w; x++) {
|
|
228
|
+
const from = x + y * w
|
|
229
|
+
const to = y + (h2 - x - 1) * w2
|
|
230
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
231
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
232
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
233
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} break;
|
|
237
|
+
case 7 : {
|
|
238
|
+
w2 = h
|
|
239
|
+
h2 = w
|
|
240
|
+
canvas.width = w2
|
|
241
|
+
canvas.height = h2
|
|
242
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
243
|
+
const toData = toIData.data
|
|
244
|
+
for (let y = 0; y < h; y++) {
|
|
245
|
+
for (let x = 0; x < w; x++) {
|
|
246
|
+
const from = x + y * w
|
|
247
|
+
const to = (w2 - y - 1) + (h2 - x - 1) * w2
|
|
248
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
249
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
250
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
251
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} break;
|
|
255
|
+
case 8 : {
|
|
256
|
+
w2 = h
|
|
257
|
+
h2 = w
|
|
258
|
+
canvas.width = w2
|
|
259
|
+
canvas.height = h2
|
|
260
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
261
|
+
const toData = toIData.data
|
|
262
|
+
for (let y = 0; y < h; y++) {
|
|
263
|
+
for (let x = 0; x < w; x++) {
|
|
264
|
+
const from = x + y * w
|
|
265
|
+
const to = (w2 - y - 1) + x * w2
|
|
266
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
267
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
268
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
269
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} break;
|
|
273
|
+
default: {
|
|
274
|
+
w2 = w
|
|
275
|
+
h2 = h
|
|
276
|
+
canvas.width = w2
|
|
277
|
+
canvas.height = h2
|
|
278
|
+
toIData = canvas.getContext("2d").getImageData(0, 0, w2, h2)
|
|
279
|
+
const toData = toIData.data
|
|
280
|
+
for (let y = 0; y < h; y++) {
|
|
281
|
+
for (let x = 0; x < w; x++) {
|
|
282
|
+
const from = x + y * w
|
|
283
|
+
const to = x + y * w
|
|
284
|
+
toData[(to << 2) + 0] = fromData[(from << 2) + 0]
|
|
285
|
+
toData[(to << 2) + 1] = fromData[(from << 2) + 1]
|
|
286
|
+
toData[(to << 2) + 2] = fromData[(from << 2) + 2]
|
|
287
|
+
toData[(to << 2) + 3] = fromData[(from << 2) + 3]
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
canvas.getContext("2d").putImageData(toIData, 0, 0)
|
|
294
|
+
return canvas
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function imageToCanvas(image) {
|
|
298
|
+
const canvas= document.createElement("canvas")
|
|
299
|
+
canvas.width = image.width
|
|
300
|
+
canvas.height = image.height
|
|
301
|
+
const ctx = canvas.getContext("2d")
|
|
302
|
+
ctx.save()
|
|
303
|
+
ctx.drawImage(image, 0, 0)
|
|
304
|
+
return canvas
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function resizeCanvas(canvas, w, h) {
|
|
308
|
+
const img = canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height)
|
|
309
|
+
const img2 = canvas.getContext("2d").getImageData(0, 0, w, h)
|
|
310
|
+
const inputData = img.data
|
|
311
|
+
const outputData = img2.data
|
|
312
|
+
resampleHermite(inputData, canvas.width, canvas.height, outputData, w, h)
|
|
313
|
+
canvas.getContext("2d").clearRect(0, 0, Math.max(canvas.width, w), Math.max(canvas.height, h))
|
|
314
|
+
canvas.width = w
|
|
315
|
+
canvas.height = h
|
|
316
|
+
canvas.getContext("2d").putImageData(img2, 0, 0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function cropCanvas(canvas, x, y, w, h) {
|
|
320
|
+
const img = canvas.getContext("2d").getImageData(x, y, w, h)
|
|
321
|
+
canvas.width=w
|
|
322
|
+
canvas.height=h
|
|
323
|
+
canvas.getContext("2d").putImageData(img, 0, 0)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function resampleHermite(src, w, h, dest, w2, h2){
|
|
327
|
+
const startTime = Date.now()
|
|
328
|
+
const ratio_w = w / w2
|
|
329
|
+
const ratio_h = h / h2
|
|
330
|
+
const ratio_w_half = Math.ceil(ratio_w/2)
|
|
331
|
+
const ratio_h_half = Math.ceil(ratio_h/2)
|
|
332
|
+
|
|
333
|
+
for(let j = 0; j < h2; j++){
|
|
334
|
+
for(let i = 0; i < w2; i++){
|
|
335
|
+
const x2 = (i + j*w2) * 4;
|
|
336
|
+
const center_y = (j + 0.5) * ratio_h
|
|
337
|
+
|
|
338
|
+
let weight = 0;
|
|
339
|
+
let weights = 0;
|
|
340
|
+
let weights_alpha = 0;
|
|
341
|
+
let gx_r = 0
|
|
342
|
+
let gx_g = 0
|
|
343
|
+
let gx_b = 0
|
|
344
|
+
let gx_a = 0
|
|
345
|
+
|
|
346
|
+
for(let yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++){
|
|
347
|
+
const dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half
|
|
348
|
+
const center_x = (i + 0.5) * ratio_w
|
|
349
|
+
const f0 = dy*dy //pre-calc part of f
|
|
350
|
+
for(let xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++){
|
|
351
|
+
let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half
|
|
352
|
+
const f = Math.sqrt(f0 + dx*dx)
|
|
353
|
+
if(f >= -1 && f <= 1){
|
|
354
|
+
//hermite filter
|
|
355
|
+
weight = 2 * f*f*f - 3*f*f + 1
|
|
356
|
+
if(weight > 0){
|
|
357
|
+
dx = 4*(xx + yy*f)
|
|
358
|
+
//alpha
|
|
359
|
+
gx_a += weight * src[dx + 3]
|
|
360
|
+
weights_alpha += weight
|
|
361
|
+
//colors
|
|
362
|
+
if(src[dx + 3] < 255)
|
|
363
|
+
weight = weight * src[dx + 3] / 250
|
|
364
|
+
gx_r += weight * src[dx]
|
|
365
|
+
gx_g += weight * src[dx + 1]
|
|
366
|
+
gx_b += weight * src[dx + 2]
|
|
367
|
+
weights += weight
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
dest[x2] = gx_r / weights
|
|
373
|
+
dest[x2 + 1] = gx_g / weights
|
|
374
|
+
dest[x2 + 2] = gx_b / weights
|
|
375
|
+
dest[x2 + 3] = gx_a / weights_alpha
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
console.log(`hermite resample took ${Date.now() - startTime}ms`)
|
|
379
|
+
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getDataURIData(dataURI) {
|
|
383
|
+
// convert base64/URLEncoded data component to raw binary data held in a string
|
|
384
|
+
let byteString
|
|
385
|
+
if (dataURI.split(',')[0].indexOf('base64') >= 0)
|
|
386
|
+
byteString = atob(dataURI.split(',')[1])
|
|
387
|
+
else
|
|
388
|
+
byteString = unescape(dataURI.split(',')[1])
|
|
389
|
+
// write the bytes of the string to a typed array
|
|
390
|
+
let ia = new Uint8Array(byteString.length)
|
|
391
|
+
for (let i = 0; i < byteString.length; i++) {
|
|
392
|
+
ia[i] = byteString.charCodeAt(i)
|
|
393
|
+
}
|
|
394
|
+
return ia
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getDataURIMime(dataURI) {
|
|
398
|
+
return dataURI.split(',')[0].split(':')[1].split(';')[0]
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
const methods = {
|
|
403
|
+
getOrientation,
|
|
404
|
+
loadImageUpload,
|
|
405
|
+
resize,
|
|
406
|
+
changeOrientation,
|
|
407
|
+
cancelOrientation,
|
|
408
|
+
imageToCanvas,
|
|
409
|
+
resizeCanvas,
|
|
410
|
+
cropCanvas,
|
|
411
|
+
resampleHermite,
|
|
412
|
+
getDataURIData,
|
|
413
|
+
getDataURIMime,
|
|
414
|
+
blobToDataUrl,
|
|
415
|
+
hasAlpha,
|
|
416
|
+
isExifOrientationSupported,
|
|
417
|
+
loadImage
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export {
|
|
421
|
+
getOrientation,
|
|
422
|
+
loadImageUpload,
|
|
423
|
+
resize,
|
|
424
|
+
changeOrientation,
|
|
425
|
+
cancelOrientation,
|
|
426
|
+
imageToCanvas,
|
|
427
|
+
resizeCanvas,
|
|
428
|
+
cropCanvas,
|
|
429
|
+
resampleHermite,
|
|
430
|
+
getDataURIData,
|
|
431
|
+
getDataURIMime,
|
|
432
|
+
blobToDataUrl,
|
|
433
|
+
hasAlpha,
|
|
434
|
+
isExifOrientationSupported,
|
|
435
|
+
loadImage
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export default methods
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
imageToCanvas, loadImageUpload, cancelOrientation, hasAlpha, isExifOrientationSupported
|
|
3
|
+
} from "./imageUtils.js"
|
|
4
|
+
import imageResizer from "./imageResizer"
|
|
5
|
+
|
|
6
|
+
async function preProcessImageFile({ file, image, canvas }, config) {
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
maxUploadSize = 10 * 1024 * 1024,
|
|
10
|
+
maxUploadWidth = 2048,
|
|
11
|
+
maxUploadHeight = 2048,
|
|
12
|
+
maxProcessableSize = 50 * 1024 * 1025,
|
|
13
|
+
maxProcessableWidth = 10000,
|
|
14
|
+
maxProcessableHeight = 10000,
|
|
15
|
+
maxProcessablePixels = 6000 * 6000
|
|
16
|
+
} = config
|
|
17
|
+
|
|
18
|
+
if(!file && !image && !canvas) {
|
|
19
|
+
throw new Error('noImage')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (file?.size > maxProcessableSize) {
|
|
23
|
+
throw new Error("tooBig")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if(!image) {
|
|
27
|
+
if(canvas) {
|
|
28
|
+
image = {
|
|
29
|
+
width: canvas.width,
|
|
30
|
+
height: canvas.height,
|
|
31
|
+
orientation: 0,
|
|
32
|
+
type: config.fileType || 'image/png',
|
|
33
|
+
image: null // not needed because we have canvas
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
image = await loadImageUpload(file)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (image.width > maxProcessableWidth
|
|
41
|
+
|| image.height > maxProcessableHeight
|
|
42
|
+
|| (image.width * image.height) > maxProcessablePixels) {
|
|
43
|
+
throw new Error("tooBig")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let processingNeeded =
|
|
47
|
+
image.width > maxUploadWidth
|
|
48
|
+
|| image.height > maxUploadHeight
|
|
49
|
+
|| image.orientation
|
|
50
|
+
|
|
51
|
+
if(!processingNeeded) {
|
|
52
|
+
if(!file) {
|
|
53
|
+
if(!canvas) canvas = imageToCanvas(image.image)
|
|
54
|
+
file = await new Promise(
|
|
55
|
+
(resolve, reject) => canvas.toBlob(resolve, config.fileType || 'image/png')
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
processingNeeded ||= file.size > maxUploadSize
|
|
61
|
+
|
|
62
|
+
if(!processingNeeded) {
|
|
63
|
+
return {
|
|
64
|
+
blob: file, canvas,
|
|
65
|
+
size: { width: image.width, height: image.height }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if(!canvas) canvas = imageToCanvas(image.image)
|
|
70
|
+
|
|
71
|
+
if(image.width > maxUploadWidth || image.height > maxUploadHeight ) { /// RESIZING NEEDED
|
|
72
|
+
const maxRatio = maxUploadWidth / maxUploadHeight
|
|
73
|
+
const inputRatio = image.width / image.height
|
|
74
|
+
|
|
75
|
+
let targetWidth, targetHeight
|
|
76
|
+
if(inputRatio > maxRatio) { /// scale to max width
|
|
77
|
+
targetWidth = maxUploadWidth
|
|
78
|
+
targetHeight = Math.round(maxUploadWidth / inputRatio)
|
|
79
|
+
} else { /// scale to max height
|
|
80
|
+
targetWidth = Math.round(maxUploadHeight * inputRatio)
|
|
81
|
+
targetHeight = maxUploadHeight
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`RESIZING ${image.width}x${image.height} => ${targetWidth}x${targetHeight}`)
|
|
85
|
+
|
|
86
|
+
let destCanvas = document.createElement('canvas')
|
|
87
|
+
if(image.orientation > 4 && !(await isExifOrientationSupported())) { // Swap dimmensions
|
|
88
|
+
destCanvas.width = targetHeight
|
|
89
|
+
destCanvas.height = targetWidth
|
|
90
|
+
} else {
|
|
91
|
+
destCanvas.width = targetWidth
|
|
92
|
+
destCanvas.height = targetHeight
|
|
93
|
+
}
|
|
94
|
+
await imageResizer.resize(canvas, destCanvas, {
|
|
95
|
+
unsharpAmount: 80,
|
|
96
|
+
unsharpRadius: 0.6,
|
|
97
|
+
unsharpThreshold: 2,
|
|
98
|
+
alpha: hasAlpha(canvas)
|
|
99
|
+
})
|
|
100
|
+
canvas = destCanvas
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if(image.orientation && !(await isExifOrientationSupported())) {
|
|
104
|
+
console.log("CANCEL ORIENTATION", image.orientation)
|
|
105
|
+
canvas = cancelOrientation(canvas, image.orientation)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const blob = await new Promise(
|
|
109
|
+
(resolve, reject) => canvas.toBlob(resolve, file?.type || config.fileType || 'image/png')
|
|
110
|
+
)
|
|
111
|
+
console.log("OUTPUT CANVAS", canvas.width, canvas.height)
|
|
112
|
+
console.log("OUTPUT BLOB", blob)
|
|
113
|
+
blob.name = file.name
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
blob, canvas,
|
|
117
|
+
size: { width: canvas.width, height: canvas.height }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default preProcessImageFile
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMemoryHistory,
|
|
3
|
+
createRouter as _createRouter,
|
|
4
|
+
createWebHistory
|
|
5
|
+
} from 'vue-router'
|
|
6
|
+
|
|
7
|
+
import { dbAdminRoutes } from "@live-change/db-admin"
|
|
8
|
+
|
|
9
|
+
export function wysiwygRoutes(config = {}) {
|
|
10
|
+
const { prefix = '/', route = (r) => r } = config
|
|
11
|
+
return [
|
|
12
|
+
route({
|
|
13
|
+
name: 'upload:test', path: prefix + 'upload', meta: { },
|
|
14
|
+
component: () => import("./UploadTest.vue"),
|
|
15
|
+
props: {
|
|
16
|
+
}
|
|
17
|
+
}),
|
|
18
|
+
route({
|
|
19
|
+
name: 'editor:test', path: prefix + '', meta: { },
|
|
20
|
+
component: () => import("./EditorTest.vue"),
|
|
21
|
+
props: {
|
|
22
|
+
}
|
|
23
|
+
}),
|
|
24
|
+
|
|
25
|
+
...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) })
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function sitemap(route, api) {
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function createRouter(app, config) {
|
|
35
|
+
const router = _createRouter({
|
|
36
|
+
history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
|
|
37
|
+
routes: wysiwygRoutes(config)
|
|
38
|
+
})
|
|
39
|
+
return router
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
|
|
3
|
+
import baseViteConfig from '@live-change/frontend-base/vite-config.js'
|
|
4
|
+
|
|
5
|
+
export default defineConfig(async ({ command, mode }) => {
|
|
6
|
+
const baseConfig = (await baseViteConfig({ command, mode }))
|
|
7
|
+
return {
|
|
8
|
+
...baseConfig,
|
|
9
|
+
|
|
10
|
+
resolve: {
|
|
11
|
+
alias: [
|
|
12
|
+
...baseConfig.resolve.alias,
|
|
13
|
+
/* { find: 'vue', replacement: 'vue/dist/vue.esm-bundler.js' },
|
|
14
|
+
{ find: 'vue/server-renderer', replacement: 'vue/server-renderer' },*/
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
package/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ImageUpload from "./front/src/ImageUpload.js"
|
|
2
|
+
import ImageEditor from "./front/src/ImageEditor.vue"
|
|
3
|
+
import Image from "./front/src/Image.vue"
|
|
4
|
+
import {uploadImage, imageUploads} from "./front/src/imageUploads.js";
|
|
5
|
+
import preProcessImageFile from "./front/src/preprocessImageFile.js";
|
|
6
|
+
|
|
7
|
+
export { ImageUpload, Image, uploadImage, preProcessImageFile, imageUploads, ImageEditor }
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/image-frontend",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
|
|
6
|
+
"localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
|
|
7
|
+
"localDev": "lcli localDev --enableSessions",
|
|
8
|
+
"dev": "lcli dev --enableSessions",
|
|
9
|
+
"ssrDev": "lcli ssrDev --enableSessions",
|
|
10
|
+
"serveAllMem": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions --withDb --dbBackend mem --createDb",
|
|
11
|
+
"serveAll": "cross-env NODE_ENV=production lcli ssrServer --withApi --withServices --updateServices --enableSessions",
|
|
12
|
+
"serve": "cross-env NODE_ENV=production lcli ssrServer --enableSessions",
|
|
13
|
+
"apiServer": "lcli apiServer --enableSessions",
|
|
14
|
+
"devApiServer": "lcli devApiServer --enableSessions",
|
|
15
|
+
"memApiServer": "lcli memApiServer --enableSessions",
|
|
16
|
+
"build": "cd front; yarn build:client && yarn build:server",
|
|
17
|
+
"build:client": "cd front; vite build --ssrManifest --outDir dist/client",
|
|
18
|
+
"build:server": "cd front; vite build --ssr src/entry-server.js --outDir dist/server",
|
|
19
|
+
"generate": "vite build --ssrManifest --outDir dist/static && yarn build:server && node prerender",
|
|
20
|
+
"debug": "node --inspect-brk server"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@live-change/framework": "0.6.5",
|
|
24
|
+
"@live-change/cli": "0.6.5",
|
|
25
|
+
"@live-change/dao": "0.4.13",
|
|
26
|
+
"@live-change/dao-vue3": "0.4.13",
|
|
27
|
+
"@live-change/dao-websocket": "0.4.13",
|
|
28
|
+
"@live-change/vue3-ssr": "0.2.14",
|
|
29
|
+
"@live-change/vue3-components": "0.2.12",
|
|
30
|
+
"@live-change/session-service": "0.2.39",
|
|
31
|
+
"@live-change/image-service": "0.2.37",
|
|
32
|
+
"@vitejs/plugin-vue": "^2.3.1",
|
|
33
|
+
"@vitejs/plugin-vue-jsx": "^1.3.10",
|
|
34
|
+
"@vue/compiler-sfc": "^3.2.33",
|
|
35
|
+
"@vueuse/core": "^8.3.1",
|
|
36
|
+
"v-shared-element": "3.1.0",
|
|
37
|
+
"vue3-scroll-border": "0.1.2",
|
|
38
|
+
"codeceptjs-assert": "^0.0.5",
|
|
39
|
+
"compression": "^1.7.4",
|
|
40
|
+
"cross-env": "^7.0.3",
|
|
41
|
+
"get-port-sync": "1.0.1",
|
|
42
|
+
"primeicons": "^5.0.0",
|
|
43
|
+
"primevue": "^3.15.0",
|
|
44
|
+
"primeflex": "^3.2.1",
|
|
45
|
+
"rollup-plugin-node-builtins": "^2.1.2",
|
|
46
|
+
"serialize-javascript": "^6.0.0",
|
|
47
|
+
"serve-static": "^1.15.0",
|
|
48
|
+
"vite": "^2.9.6",
|
|
49
|
+
"vue": "^3.2.33",
|
|
50
|
+
"vue-meta": "^3.0.0-alpha.9",
|
|
51
|
+
"vue-router": "^4.0.14",
|
|
52
|
+
"vite-plugin-compression": "0.5.1",
|
|
53
|
+
"vite-plugin-vue-images": "^0.6.1",
|
|
54
|
+
"rollup-plugin-visualizer": "5.6.0",
|
|
55
|
+
"@tiptap/vue-3": "2.0.0-beta.91",
|
|
56
|
+
"@tiptap/starter-kit": "^2.0.0-beta.185",
|
|
57
|
+
"@tiptap/extension-underline": "2.0.0-beta.23",
|
|
58
|
+
"@tiptap/extension-highlight": "^2.0.0-beta.33",
|
|
59
|
+
"@fortawesome/fontawesome-free": "^6.1.1",
|
|
60
|
+
"pretty-bytes": "^6.0.0",
|
|
61
|
+
"pica": "^9.0.1"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@live-change/codeceptjs-helper": "0.6.5",
|
|
65
|
+
"@wdio/selenium-standalone-service": "^7.19.5",
|
|
66
|
+
"codeceptjs": "^3.3.1",
|
|
67
|
+
"playwright": "^1.21.1",
|
|
68
|
+
"random-profile-generator": "^2.3.0",
|
|
69
|
+
"txtgen": "^3.0.1",
|
|
70
|
+
"generate-password": "1.7.0",
|
|
71
|
+
"webdriverio": "^7.19.5"
|
|
72
|
+
},
|
|
73
|
+
"author": "",
|
|
74
|
+
"license": "ISC",
|
|
75
|
+
"description": ""
|
|
76
|
+
}
|