Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/imageResize/browser/imageResizeService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { decodeBase64, VSBuffer } from '../../../base/common/buffer.js';
7
import { joinPath } from '../../../base/common/resources.js';
8
import { URI } from '../../../base/common/uri.js';
9
import { IFileService } from '../../files/common/files.js';
10
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
11
import { ILogService } from '../../log/common/log.js';
12
import { IImageResizeService } from '../common/imageResizeService.js';
13
14
15
export class ImageResizeService implements IImageResizeService {
16
17
declare readonly _serviceBrand: undefined;
18
19
/**
20
* Resizes an image provided as a UInt8Array string. Resizing is based on Open AI's algorithm for tokenzing images.
21
* https://platform.openai.com/docs/guides/vision#calculating-costs
22
* @param data - The UInt8Array string of the image to resize.
23
* @returns A promise that resolves to the UInt8Array string of the resized image.
24
*/
25
26
async resizeImage(data: Uint8Array | string, mimeType?: string): Promise<Uint8Array> {
27
const isGif = mimeType === 'image/gif';
28
29
if (typeof data === 'string') {
30
data = this.convertStringToUInt8Array(data);
31
}
32
33
return new Promise((resolve, reject) => {
34
const blob = new Blob([data as Uint8Array<ArrayBuffer>], { type: mimeType });
35
const img = new Image();
36
const url = URL.createObjectURL(blob);
37
img.src = url;
38
39
img.onload = () => {
40
URL.revokeObjectURL(url);
41
let { width, height } = img;
42
43
if ((width <= 768 || height <= 768) && !isGif) {
44
resolve(data);
45
return;
46
}
47
48
// Calculate the new dimensions while maintaining the aspect ratio
49
if (width > 2048 || height > 2048) {
50
const scaleFactor = 2048 / Math.max(width, height);
51
width = Math.round(width * scaleFactor);
52
height = Math.round(height * scaleFactor);
53
}
54
55
const scaleFactor = 768 / Math.min(width, height);
56
width = Math.round(width * scaleFactor);
57
height = Math.round(height * scaleFactor);
58
59
const canvas = document.createElement('canvas');
60
canvas.width = width;
61
canvas.height = height;
62
const ctx = canvas.getContext('2d');
63
if (ctx) {
64
ctx.drawImage(img, 0, 0, width, height);
65
66
const jpegTypes = ['image/jpeg', 'image/jpg'];
67
const outputMimeType = mimeType && jpegTypes.includes(mimeType) ? 'image/jpeg' : 'image/png';
68
69
canvas.toBlob(blob => {
70
if (blob) {
71
const reader = new FileReader();
72
reader.onload = () => {
73
resolve(new Uint8Array(reader.result as ArrayBuffer));
74
};
75
reader.onerror = (error) => reject(error);
76
reader.readAsArrayBuffer(blob);
77
} else {
78
reject(new Error('Failed to create blob from canvas'));
79
}
80
}, outputMimeType);
81
} else {
82
reject(new Error('Failed to get canvas context'));
83
}
84
};
85
img.onerror = (error) => {
86
URL.revokeObjectURL(url);
87
reject(error);
88
};
89
});
90
}
91
92
convertStringToUInt8Array(data: string): Uint8Array {
93
const base64Data = data.includes(',') ? data.split(',')[1] : data;
94
if (this.isValidBase64(base64Data)) {
95
return decodeBase64(base64Data).buffer;
96
}
97
return new TextEncoder().encode(data);
98
}
99
100
// Only used for URLs
101
convertUint8ArrayToString(data: Uint8Array): string {
102
try {
103
const decoder = new TextDecoder();
104
const decodedString = decoder.decode(data);
105
return decodedString;
106
} catch {
107
return '';
108
}
109
}
110
111
isValidBase64(str: string): boolean {
112
try {
113
decodeBase64(str);
114
return true;
115
} catch {
116
return false;
117
}
118
}
119
120
async createFileForMedia(fileService: IFileService, imagesFolder: URI, dataTransfer: Uint8Array, mimeType: string): Promise<URI | undefined> {
121
const exists = await fileService.exists(imagesFolder);
122
if (!exists) {
123
await fileService.createFolder(imagesFolder);
124
}
125
126
const ext = mimeType.split('/')[1] || 'png';
127
const filename = `image-${Date.now()}.${ext}`;
128
const fileUri = joinPath(imagesFolder, filename);
129
130
const buffer = VSBuffer.wrap(dataTransfer);
131
await fileService.writeFile(fileUri, buffer);
132
133
return fileUri;
134
}
135
136
async cleanupOldImages(fileService: IFileService, logService: ILogService, imagesFolder: URI): Promise<void> {
137
const exists = await fileService.exists(imagesFolder);
138
if (!exists) {
139
return;
140
}
141
142
const duration = 7 * 24 * 60 * 60 * 1000; // 7 days
143
const files = await fileService.resolve(imagesFolder);
144
if (!files.children) {
145
return;
146
}
147
148
await Promise.all(files.children.map(async (file) => {
149
try {
150
const timestamp = this.getTimestampFromFilename(file.name);
151
if (timestamp && (Date.now() - timestamp > duration)) {
152
await fileService.del(file.resource);
153
}
154
} catch (err) {
155
logService.error('Failed to clean up old images', err);
156
}
157
}));
158
}
159
160
getTimestampFromFilename(filename: string): number | undefined {
161
const match = filename.match(/image-(\d+)\./);
162
if (match) {
163
return parseInt(match[1], 10);
164
}
165
return undefined;
166
}
167
168
169
}
170
171
registerSingleton(IImageResizeService, ImageResizeService, InstantiationType.Delayed);
172
173