vue3-image-compressor 1.0.4
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 +174 -0
- package/dist/assets/imageWorker-DyeUTFOy.js +67 -0
- package/dist/components/ImageCompressor.vue.d.ts +38 -0
- package/dist/composables/useCompression.d.ts +170 -0
- package/dist/composables/useEncoderRegistry.d.ts +17 -0
- package/dist/composables/useWorker.d.ts +11 -0
- package/dist/constants/encoders.d.ts +5 -0
- package/dist/constants/resizeMethods.d.ts +39 -0
- package/dist/index.d.ts +15 -0
- package/dist/style.css +1 -0
- package/dist/types/compression.d.ts +35 -0
- package/dist/types/encoder.d.ts +100 -0
- package/dist/types/processor.d.ts +30 -0
- package/dist/types/worker.d.ts +21 -0
- package/dist/utils/file.d.ts +23 -0
- package/dist/utils/image.d.ts +31 -0
- package/dist/vue-image-compressor.js +660 -0
- package/dist/vue-image-compressor.umd.cjs +1 -0
- package/dist/workers/imageWorker.d.ts +4 -0
- package/dist/workers/utils/emscripten.d.ts +6 -0
- package/package.json +49 -0
- package/src/components/ImageCompressor.vue +304 -0
- package/src/composables/useCompression.ts +314 -0
- package/src/composables/useEncoderRegistry.ts +70 -0
- package/src/composables/useWorker.ts +132 -0
- package/src/constants/encoders.ts +137 -0
- package/src/constants/resizeMethods.ts +23 -0
- package/src/index.ts +63 -0
- package/src/types/compression.ts +38 -0
- package/src/types/encoder.ts +144 -0
- package/src/types/processor.ts +36 -0
- package/src/types/worker.ts +29 -0
- package/src/utils/file.ts +48 -0
- package/src/utils/image.ts +90 -0
- package/src/workers/imageWorker.ts +107 -0
- package/src/workers/utils/emscripten.ts +16 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 编码器注册表 Composable
|
|
3
|
+
* 管理编码器列表、默认选项和当前选项
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref, computed } from 'vue';
|
|
7
|
+
import type { EncoderType, EncoderOptions } from '../types/encoder';
|
|
8
|
+
import { ENCODER_REGISTRY, DEFAULT_ENCODER_OPTIONS, ENCODER_LIST } from '../constants/encoders';
|
|
9
|
+
|
|
10
|
+
export function useEncoderRegistry() {
|
|
11
|
+
const selectedEncoder = ref<EncoderType>('mozJPEG');
|
|
12
|
+
const encoderOptions = ref<Record<string, any>>({});
|
|
13
|
+
|
|
14
|
+
// 当前编码器元数据
|
|
15
|
+
const currentMeta = computed(() => ENCODER_REGISTRY[selectedEncoder.value]);
|
|
16
|
+
|
|
17
|
+
// 当前编码器默认选项
|
|
18
|
+
const currentDefaults = computed(() =>
|
|
19
|
+
DEFAULT_ENCODER_OPTIONS[selectedEncoder.value]
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// 所有可用编码器列表
|
|
23
|
+
const availableEncoders = computed(() =>
|
|
24
|
+
ENCODER_LIST.map((type) => ({
|
|
25
|
+
type,
|
|
26
|
+
label: ENCODER_REGISTRY[type].label,
|
|
27
|
+
mimeType: ENCODER_REGISTRY[type].mimeType,
|
|
28
|
+
extension: ENCODER_REGISTRY[type].extension,
|
|
29
|
+
}))
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 切换编码器时重置选项
|
|
34
|
+
*/
|
|
35
|
+
function selectEncoder(type: EncoderType) {
|
|
36
|
+
selectedEncoder.value = type;
|
|
37
|
+
encoderOptions.value = { ...DEFAULT_ENCODER_OPTIONS[type] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 更新编码器选项
|
|
42
|
+
*/
|
|
43
|
+
function updateOption(key: string, value: any) {
|
|
44
|
+
encoderOptions.value = {
|
|
45
|
+
...encoderOptions.value,
|
|
46
|
+
[key]: value,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 重置为默认选项
|
|
52
|
+
*/
|
|
53
|
+
function resetOptions() {
|
|
54
|
+
encoderOptions.value = { ...currentDefaults.value };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 初始化
|
|
58
|
+
resetOptions();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
selectedEncoder,
|
|
62
|
+
encoderOptions,
|
|
63
|
+
currentMeta,
|
|
64
|
+
currentDefaults,
|
|
65
|
+
availableEncoders,
|
|
66
|
+
selectEncoder,
|
|
67
|
+
updateOption,
|
|
68
|
+
resetOptions,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker 封装 Composable
|
|
3
|
+
* 对标 Squoosh: src/client/lazy-app/worker-bridge/index.ts
|
|
4
|
+
* 使用 comlink 封装 Web Worker 通信
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ref, onUnmounted } from 'vue';
|
|
8
|
+
import type { WorkerApi } from '../types/worker';
|
|
9
|
+
|
|
10
|
+
// Worker 空闲超时时间(ms)
|
|
11
|
+
const WORKER_TIMEOUT = 10000;
|
|
12
|
+
|
|
13
|
+
export function useWorker() {
|
|
14
|
+
const isReady = ref(false);
|
|
15
|
+
const isLoading = ref(false);
|
|
16
|
+
const error = ref<Error | null>(null);
|
|
17
|
+
|
|
18
|
+
let worker: Worker | null = null;
|
|
19
|
+
let workerApi: WorkerApi | null = null;
|
|
20
|
+
let timeoutId: number | null = null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 启动 Worker
|
|
24
|
+
*/
|
|
25
|
+
function startWorker() {
|
|
26
|
+
if (worker) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
isLoading.value = true;
|
|
30
|
+
// 使用 Vite 的 ?worker 导入方式
|
|
31
|
+
// 实际项目中需要配置 worker 文件路径
|
|
32
|
+
worker = new Worker(
|
|
33
|
+
new URL('../workers/imageWorker.ts', import.meta.url),
|
|
34
|
+
{ type: 'module' }
|
|
35
|
+
);
|
|
36
|
+
isReady.value = true;
|
|
37
|
+
error.value = null;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
error.value = err instanceof Error ? err : new Error('Failed to start worker');
|
|
40
|
+
console.error('Worker start failed:', err);
|
|
41
|
+
} finally {
|
|
42
|
+
isLoading.value = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 终止 Worker
|
|
48
|
+
*/
|
|
49
|
+
function terminateWorker() {
|
|
50
|
+
if (timeoutId) {
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
|
+
timeoutId = null;
|
|
53
|
+
}
|
|
54
|
+
if (worker) {
|
|
55
|
+
worker.terminate();
|
|
56
|
+
worker = null;
|
|
57
|
+
workerApi = null;
|
|
58
|
+
isReady.value = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 设置 Worker 空闲超时回收
|
|
64
|
+
*/
|
|
65
|
+
function scheduleTermination() {
|
|
66
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
67
|
+
timeoutId = window.setTimeout(() => {
|
|
68
|
+
terminateWorker();
|
|
69
|
+
}, WORKER_TIMEOUT);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 获取 Worker API(懒加载)
|
|
74
|
+
*/
|
|
75
|
+
async function getWorkerApi(): Promise<WorkerApi> {
|
|
76
|
+
if (!worker) {
|
|
77
|
+
startWorker();
|
|
78
|
+
}
|
|
79
|
+
if (!worker) {
|
|
80
|
+
throw new Error('Worker failed to initialize');
|
|
81
|
+
}
|
|
82
|
+
// 简化版:直接返回 worker 的 postMessage API
|
|
83
|
+
// 实际使用 comlink 时:return wrap<WorkerApi>(worker);
|
|
84
|
+
return worker as any;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 执行 Worker 任务(带取消支持)
|
|
89
|
+
*/
|
|
90
|
+
async function executeTask<T>(
|
|
91
|
+
signal: AbortSignal,
|
|
92
|
+
task: () => Promise<T>
|
|
93
|
+
): Promise<T> {
|
|
94
|
+
if (signal.aborted) {
|
|
95
|
+
throw new DOMException('AbortError', 'AbortError');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clearTimeout(timeoutId!);
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const onAbort = () => {
|
|
102
|
+
terminateWorker();
|
|
103
|
+
reject(new DOMException('AbortError', 'AbortError'));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
signal.addEventListener('abort', onAbort);
|
|
107
|
+
|
|
108
|
+
task()
|
|
109
|
+
.then(resolve)
|
|
110
|
+
.catch(reject)
|
|
111
|
+
.finally(() => {
|
|
112
|
+
signal.removeEventListener('abort', onAbort);
|
|
113
|
+
scheduleTermination();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 组件卸载时清理 Worker
|
|
119
|
+
onUnmounted(() => {
|
|
120
|
+
terminateWorker();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
isReady,
|
|
125
|
+
isLoading,
|
|
126
|
+
error,
|
|
127
|
+
worker,
|
|
128
|
+
getWorkerApi,
|
|
129
|
+
executeTask,
|
|
130
|
+
terminateWorker,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 编码器常量配置
|
|
3
|
+
* 对标 Squoosh: src/features/encoders/x/shared/meta.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { EncoderMeta, EncoderType } from '../types/encoder';
|
|
7
|
+
|
|
8
|
+
export const ENCODER_REGISTRY: Record<EncoderType, EncoderMeta> = {
|
|
9
|
+
mozJPEG: {
|
|
10
|
+
label: 'MozJPEG',
|
|
11
|
+
mimeType: 'image/jpeg',
|
|
12
|
+
extension: 'jpg',
|
|
13
|
+
},
|
|
14
|
+
webP: {
|
|
15
|
+
label: 'WebP',
|
|
16
|
+
mimeType: 'image/webp',
|
|
17
|
+
extension: 'webp',
|
|
18
|
+
},
|
|
19
|
+
avif: {
|
|
20
|
+
label: 'AVIF',
|
|
21
|
+
mimeType: 'image/avif',
|
|
22
|
+
extension: 'avif',
|
|
23
|
+
},
|
|
24
|
+
jxl: {
|
|
25
|
+
label: 'JPEG XL',
|
|
26
|
+
mimeType: 'image/jxl',
|
|
27
|
+
extension: 'jxl',
|
|
28
|
+
},
|
|
29
|
+
oxiPNG: {
|
|
30
|
+
label: 'OxiPNG',
|
|
31
|
+
mimeType: 'image/png',
|
|
32
|
+
extension: 'png',
|
|
33
|
+
},
|
|
34
|
+
browserJPEG: {
|
|
35
|
+
label: 'Browser JPEG',
|
|
36
|
+
mimeType: 'image/jpeg',
|
|
37
|
+
extension: 'jpg',
|
|
38
|
+
},
|
|
39
|
+
browserPNG: {
|
|
40
|
+
label: 'Browser PNG',
|
|
41
|
+
mimeType: 'image/png',
|
|
42
|
+
extension: 'png',
|
|
43
|
+
},
|
|
44
|
+
browserGIF: {
|
|
45
|
+
label: 'Browser GIF',
|
|
46
|
+
mimeType: 'image/gif',
|
|
47
|
+
extension: 'gif',
|
|
48
|
+
},
|
|
49
|
+
qoi: {
|
|
50
|
+
label: 'QOI',
|
|
51
|
+
mimeType: 'image/qoi',
|
|
52
|
+
extension: 'qoi',
|
|
53
|
+
},
|
|
54
|
+
wp2: {
|
|
55
|
+
label: 'WebP2',
|
|
56
|
+
mimeType: 'image/webp2',
|
|
57
|
+
extension: 'wp2',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_ENCODER_OPTIONS: Record<EncoderType, Record<string, any>> = {
|
|
62
|
+
mozJPEG: {
|
|
63
|
+
quality: 75,
|
|
64
|
+
baseline: false,
|
|
65
|
+
arithmetic: false,
|
|
66
|
+
progressive: true,
|
|
67
|
+
optimize_coding: true,
|
|
68
|
+
smoothing: 0,
|
|
69
|
+
color_space: 3,
|
|
70
|
+
quant_table: 3,
|
|
71
|
+
trellis_multipass: false,
|
|
72
|
+
trellis_opt_zero: false,
|
|
73
|
+
trellis_opt_table: false,
|
|
74
|
+
trellis_loops: 1,
|
|
75
|
+
auto_subsample: true,
|
|
76
|
+
chroma_subsample: 2,
|
|
77
|
+
separate_chroma_quality: false,
|
|
78
|
+
chroma_quality: 75,
|
|
79
|
+
},
|
|
80
|
+
webP: {
|
|
81
|
+
quality: 75,
|
|
82
|
+
target_size: 0,
|
|
83
|
+
target_PSNR: 0,
|
|
84
|
+
method: 4,
|
|
85
|
+
sns_strength: 50,
|
|
86
|
+
filter_strength: 60,
|
|
87
|
+
filter_sharpness: 0,
|
|
88
|
+
filter_type: 1,
|
|
89
|
+
partitions: 0,
|
|
90
|
+
segments: 4,
|
|
91
|
+
pass: 1,
|
|
92
|
+
show_compressed: 0,
|
|
93
|
+
preprocessing: 0,
|
|
94
|
+
autofilter: 0,
|
|
95
|
+
partition_limit: 0,
|
|
96
|
+
alpha_compression: 1,
|
|
97
|
+
alpha_filtering: 1,
|
|
98
|
+
alpha_quality: 100,
|
|
99
|
+
lossless: 0,
|
|
100
|
+
exact: 0,
|
|
101
|
+
use_delta_palette: 0,
|
|
102
|
+
vlnr: 0,
|
|
103
|
+
near_lossless: 60,
|
|
104
|
+
},
|
|
105
|
+
avif: {
|
|
106
|
+
cqLevel: 33,
|
|
107
|
+
denoiseLevel: 0,
|
|
108
|
+
cqAlphaLevel: -1,
|
|
109
|
+
tileRows: 0,
|
|
110
|
+
tileCols: 0,
|
|
111
|
+
speed: 6,
|
|
112
|
+
subsample: 1,
|
|
113
|
+
chromaDeltaQ: false,
|
|
114
|
+
sharpness: 0,
|
|
115
|
+
tune: 0,
|
|
116
|
+
},
|
|
117
|
+
jxl: {
|
|
118
|
+
effort: 7,
|
|
119
|
+
quality: 75,
|
|
120
|
+
progressive: false,
|
|
121
|
+
targetPsize: 0,
|
|
122
|
+
},
|
|
123
|
+
oxiPNG: {
|
|
124
|
+
level: 2,
|
|
125
|
+
},
|
|
126
|
+
browserJPEG: {
|
|
127
|
+
quality: 0.75,
|
|
128
|
+
},
|
|
129
|
+
browserPNG: {},
|
|
130
|
+
browserGIF: {},
|
|
131
|
+
qoi: {},
|
|
132
|
+
wp2: {
|
|
133
|
+
quality: 75,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const ENCODER_LIST: EncoderType[] = Object.keys(ENCODER_REGISTRY) as EncoderType[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 重采样算法常量
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const RESIZE_METHODS = [
|
|
6
|
+
{ value: 'lanczos3', label: 'Lanczos3' },
|
|
7
|
+
{ value: 'catrom', label: 'Catrom' },
|
|
8
|
+
{ value: 'mitchell', label: 'Mitchell' },
|
|
9
|
+
{ value: 'triangle', label: 'Triangle' },
|
|
10
|
+
{ value: 'vector', label: 'Vector' },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export const FIT_METHODS = [
|
|
14
|
+
{ value: 'stretch', label: '拉伸' },
|
|
15
|
+
{ value: 'contain', label: '适应' },
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export const ROTATE_OPTIONS = [
|
|
19
|
+
{ value: 0, label: '不旋转' },
|
|
20
|
+
{ value: 90, label: '顺时针 90°' },
|
|
21
|
+
{ value: 180, label: '旋转 180°' },
|
|
22
|
+
{ value: 270, label: '逆时针 90°' },
|
|
23
|
+
] as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue3 Image Compressor 组件库入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 组件
|
|
6
|
+
export { default as ImageCompressor } from './components/ImageCompressor.vue';
|
|
7
|
+
|
|
8
|
+
// Composables
|
|
9
|
+
export { useCompression } from './composables/useCompression';
|
|
10
|
+
export { useEncoderRegistry } from './composables/useEncoderRegistry';
|
|
11
|
+
export { useWorker } from './composables/useWorker';
|
|
12
|
+
|
|
13
|
+
// 类型
|
|
14
|
+
export type {
|
|
15
|
+
EncoderType,
|
|
16
|
+
EncoderMeta,
|
|
17
|
+
EncoderOptions,
|
|
18
|
+
EncoderState,
|
|
19
|
+
MozJpegEncodeOptions,
|
|
20
|
+
WebPEncodeOptions,
|
|
21
|
+
AvifEncodeOptions,
|
|
22
|
+
JxlEncodeOptions,
|
|
23
|
+
OxiPngEncodeOptions,
|
|
24
|
+
BrowserJpegEncodeOptions,
|
|
25
|
+
Wp2EncodeOptions,
|
|
26
|
+
} from './types/encoder';
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
ResizeOptions,
|
|
30
|
+
QuantizeOptions,
|
|
31
|
+
RotateOptions,
|
|
32
|
+
ProcessorState,
|
|
33
|
+
} from './types/processor';
|
|
34
|
+
|
|
35
|
+
export type {
|
|
36
|
+
CompressionOptions,
|
|
37
|
+
CompressionResult,
|
|
38
|
+
ImageInfo,
|
|
39
|
+
} from './types/compression';
|
|
40
|
+
|
|
41
|
+
export type { WorkerApi } from './types/worker';
|
|
42
|
+
|
|
43
|
+
// 常量
|
|
44
|
+
export { ENCODER_REGISTRY, DEFAULT_ENCODER_OPTIONS, ENCODER_LIST } from './constants/encoders';
|
|
45
|
+
export { RESIZE_METHODS, FIT_METHODS, ROTATE_OPTIONS } from './constants/resizeMethods';
|
|
46
|
+
|
|
47
|
+
// 工具
|
|
48
|
+
export {
|
|
49
|
+
blobToImageData,
|
|
50
|
+
imageDataToBlob,
|
|
51
|
+
getImageDimensions,
|
|
52
|
+
sniffMimeType,
|
|
53
|
+
canDecodeImageType,
|
|
54
|
+
arrayBufferToFile,
|
|
55
|
+
} from './utils/image';
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
formatBytes,
|
|
59
|
+
calculateSavings,
|
|
60
|
+
generateCompressedFilename,
|
|
61
|
+
createBlobUrl,
|
|
62
|
+
revokeBlobUrl,
|
|
63
|
+
} from './utils/file';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 压缩结果类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ImageInfo {
|
|
6
|
+
file: File;
|
|
7
|
+
size: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
blobUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CompressionResult {
|
|
14
|
+
original: ImageInfo;
|
|
15
|
+
compressed: ImageInfo;
|
|
16
|
+
savingsBytes: number;
|
|
17
|
+
savingsPercent: number;
|
|
18
|
+
encoderType: string;
|
|
19
|
+
encoderOptions: Record<string, any>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CompressionOptions {
|
|
23
|
+
encoder: string;
|
|
24
|
+
encoderOptions: Record<string, any>;
|
|
25
|
+
resize?: {
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
method?: string;
|
|
30
|
+
fitMethod?: string;
|
|
31
|
+
};
|
|
32
|
+
quantize?: {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
numColors?: number;
|
|
35
|
+
dither?: number;
|
|
36
|
+
};
|
|
37
|
+
rotate?: number;
|
|
38
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 编码器类型定义
|
|
3
|
+
* 对标 Squoosh: src/client/lazy-app/feature-meta/index.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type EncoderType =
|
|
7
|
+
| 'mozJPEG'
|
|
8
|
+
| 'webP'
|
|
9
|
+
| 'avif'
|
|
10
|
+
| 'jxl'
|
|
11
|
+
| 'oxiPNG'
|
|
12
|
+
| 'browserJPEG'
|
|
13
|
+
| 'browserPNG'
|
|
14
|
+
| 'browserGIF'
|
|
15
|
+
| 'qoi'
|
|
16
|
+
| 'wp2';
|
|
17
|
+
|
|
18
|
+
export interface EncoderMeta {
|
|
19
|
+
label: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
extension: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MozJpegColorSpace {
|
|
25
|
+
GRAYSCALE: 1;
|
|
26
|
+
RGB: 2;
|
|
27
|
+
YCbCr: 3;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MozJpegEncodeOptions {
|
|
31
|
+
quality: number;
|
|
32
|
+
baseline: boolean;
|
|
33
|
+
arithmetic: boolean;
|
|
34
|
+
progressive: boolean;
|
|
35
|
+
optimize_coding: boolean;
|
|
36
|
+
smoothing: number;
|
|
37
|
+
color_space: number;
|
|
38
|
+
quant_table: number;
|
|
39
|
+
trellis_multipass: boolean;
|
|
40
|
+
trellis_opt_zero: boolean;
|
|
41
|
+
trellis_opt_table: boolean;
|
|
42
|
+
trellis_loops: number;
|
|
43
|
+
auto_subsample: boolean;
|
|
44
|
+
chroma_subsample: number;
|
|
45
|
+
separate_chroma_quality: boolean;
|
|
46
|
+
chroma_quality: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WebPEncodeOptions {
|
|
50
|
+
quality: number;
|
|
51
|
+
target_size: number;
|
|
52
|
+
target_PSNR: number;
|
|
53
|
+
method: number;
|
|
54
|
+
sns_strength: number;
|
|
55
|
+
filter_strength: number;
|
|
56
|
+
filter_sharpness: number;
|
|
57
|
+
filter_type: number;
|
|
58
|
+
partitions: number;
|
|
59
|
+
segments: number;
|
|
60
|
+
pass: number;
|
|
61
|
+
show_compressed: number;
|
|
62
|
+
preprocessing: number;
|
|
63
|
+
autofilter: number;
|
|
64
|
+
partition_limit: number;
|
|
65
|
+
alpha_compression: number;
|
|
66
|
+
alpha_filtering: number;
|
|
67
|
+
alpha_quality: number;
|
|
68
|
+
lossless: number;
|
|
69
|
+
exact: number;
|
|
70
|
+
use_delta_palette: number;
|
|
71
|
+
vlnr: number;
|
|
72
|
+
near_lossless: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface AvifEncodeOptions {
|
|
76
|
+
cqLevel: number;
|
|
77
|
+
denoiseLevel: number;
|
|
78
|
+
cqAlphaLevel: number;
|
|
79
|
+
tileRows: number;
|
|
80
|
+
tileCols: number;
|
|
81
|
+
speed: number;
|
|
82
|
+
subsample: number;
|
|
83
|
+
chromaDeltaQ: boolean;
|
|
84
|
+
sharpness: number;
|
|
85
|
+
tune: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface JxlEncodeOptions {
|
|
89
|
+
effort: number;
|
|
90
|
+
quality: number;
|
|
91
|
+
progressive: boolean;
|
|
92
|
+
targetPsize: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface OxiPngEncodeOptions {
|
|
96
|
+
level: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface BrowserJpegEncodeOptions {
|
|
100
|
+
quality: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface BrowserPngEncodeOptions {
|
|
104
|
+
// browser PNG has no options
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface BrowserGifEncodeOptions {
|
|
108
|
+
// browser GIF has no options
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface QoiEncodeOptions {
|
|
112
|
+
// QOI has no options
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface Wp2EncodeOptions {
|
|
116
|
+
quality: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type EncoderOptions =
|
|
120
|
+
| MozJpegEncodeOptions
|
|
121
|
+
| WebPEncodeOptions
|
|
122
|
+
| AvifEncodeOptions
|
|
123
|
+
| JxlEncodeOptions
|
|
124
|
+
| OxiPngEncodeOptions
|
|
125
|
+
| BrowserJpegEncodeOptions
|
|
126
|
+
| BrowserPngEncodeOptions
|
|
127
|
+
| BrowserGifEncodeOptions
|
|
128
|
+
| QoiEncodeOptions
|
|
129
|
+
| Wp2EncodeOptions;
|
|
130
|
+
|
|
131
|
+
export interface EncoderState<T extends EncoderType = EncoderType> {
|
|
132
|
+
type: T;
|
|
133
|
+
options: EncoderOptions;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type EncoderEntry = {
|
|
137
|
+
meta: EncoderMeta;
|
|
138
|
+
encode: (
|
|
139
|
+
signal: AbortSignal,
|
|
140
|
+
workerApi: any,
|
|
141
|
+
imageData: ImageData,
|
|
142
|
+
options: EncoderOptions
|
|
143
|
+
) => Promise<ArrayBuffer>;
|
|
144
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 处理器类型定义
|
|
3
|
+
* 对标 Squoosh: src/features/processors/resize/shared/meta.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ResizeOptions {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
method: 'lanczos3' | 'catrom' | 'mitchell' | 'triangle' | 'vector';
|
|
11
|
+
fitMethod: 'stretch' | 'contain';
|
|
12
|
+
premultiply: boolean;
|
|
13
|
+
linearRGB: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface QuantizeOptions {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
numColors: number;
|
|
19
|
+
dither: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RotateOptions {
|
|
23
|
+
rotate: number; // 0, 90, 180, 270
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ProcessorState {
|
|
27
|
+
resize: ResizeOptions;
|
|
28
|
+
quantize: QuantizeOptions;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PreprocessorState {
|
|
32
|
+
rotate: RotateOptions;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ResizeMethod = 'lanczos3' | 'catrom' | 'mitchell' | 'triangle' | 'vector';
|
|
36
|
+
export type FitMethod = 'stretch' | 'contain';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker API 类型定义
|
|
3
|
+
* 对标 Squoosh: src/client/lazy-app/worker-bridge/meta.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface WorkerApi {
|
|
7
|
+
// Decoders
|
|
8
|
+
avifDecode(blob: Blob): Promise<ImageData>;
|
|
9
|
+
jxlDecode(blob: Blob): Promise<ImageData>;
|
|
10
|
+
qoiDecode(blob: Blob): Promise<ImageData>;
|
|
11
|
+
webpDecode(blob: Blob): Promise<ImageData>;
|
|
12
|
+
wp2Decode(blob: Blob): Promise<ImageData>;
|
|
13
|
+
|
|
14
|
+
// Encoders
|
|
15
|
+
avifEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
16
|
+
jxlEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
17
|
+
mozjpegEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
18
|
+
oxipngEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
19
|
+
qoiEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
20
|
+
webpEncode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
21
|
+
wp2Encode(data: ImageData, options: any): Promise<Uint8Array>;
|
|
22
|
+
|
|
23
|
+
// Preprocessors
|
|
24
|
+
rotate(data: ImageData, options: any): Promise<ImageData>;
|
|
25
|
+
|
|
26
|
+
// Processors
|
|
27
|
+
quantize(data: ImageData, options: any): Promise<ImageData>;
|
|
28
|
+
resize(data: ImageData, options: any): Promise<ImageData>;
|
|
29
|
+
}
|