Skip to content

Commit 7e83bd1

Browse files
committed
feat: support auto resize image
1 parent 8aa86a7 commit 7e83bd1

8 files changed

Lines changed: 448 additions & 326 deletions

File tree

AGENTS.md

Lines changed: 363 additions & 312 deletions
Large diffs are not rendered by default.

app/components/AdminUpload.vue

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import type { CompressMultiResultOption } from '~/utils/compress'
23
import pLimit from 'p-limit'
34
45
interface FileEntry {
@@ -127,16 +128,37 @@ async function processFileExif(fileEntry: FileEntry) {
127128
128129
async function processFileCompress(fileEntry: FileEntry) {
129130
const fileType = fileEntry.file.type
130-
const formatsConfig = { ...toRaw(uploadConfig.value).formats }
131-
if (fileType === 'webp') {
132-
formatsConfig.jpeg = false
133-
formatsConfig.webp = false
134-
}
135-
if (fileType === 'avif') {
136-
formatsConfig.jpeg = false
137-
formatsConfig.webp = false
138-
formatsConfig.avif = false
131+
const baseFormats = toRaw(uploadConfig.value).formats
132+
133+
const formatsConfig: CompressMultiResultOption = {}
134+
const formatKeys = ['jpeg', 'webp', 'avif', 'thumbnail'] as const
135+
const autoResize = uploadConfig.value.enableAutoResize
136+
137+
for (const key of formatKeys) {
138+
const enabled = baseFormats[key]
139+
if (!enabled)
140+
continue
141+
if (fileType === 'image/webp' && (key === 'jpeg' || key === 'webp'))
142+
continue
143+
if (fileType === 'image/avif' && (key === 'jpeg' || key === 'webp' || key === 'avif'))
144+
continue
145+
146+
switch (key) {
147+
case 'jpeg':
148+
formatsConfig.jpeg = { target: 'jpeg', autoResize }
149+
break
150+
case 'webp':
151+
formatsConfig.webp = { target: 'webp', autoResize }
152+
break
153+
case 'avif':
154+
formatsConfig.avif = { target: 'avif', autoResize }
155+
break
156+
case 'thumbnail':
157+
formatsConfig.thumbnail = { target: 'thumbnail' }
158+
break
159+
}
139160
}
161+
140162
fileEntry.compressedFile = Object.fromEntries(Object.entries(formatsConfig).map(
141163
([type, value]) => [type, value ? 'loading' : undefined],
142164
))

app/components/UploadConfig.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ function selectProvider(providerId: string) {
5959
<ItemStatus :label="$t('compression_config.webp')" :checked="uploadConfig.formats.webp" />
6060
<ItemStatus :label="$t('compression_config.avif')" :checked="uploadConfig.formats.avif" />
6161
</Card>
62+
<ItemStatus :label="$t('compression_config.auto_resize')" :checked="uploadConfig.enableAutoResize" />
6263
<ItemStatus :label="$t('compression_config.thumbnail')" :checked="uploadConfig.formats.thumbnail" />
6364
</template>
6465
<template #config>
@@ -115,6 +116,21 @@ function selectProvider(providerId: string) {
115116
</div>
116117
</CollapsibleContent>
117118
</Collapsible>
119+
<div class="flex gap-2 items-center">
120+
<Checkbox
121+
id="enable-auto-resize"
122+
v-model="uploadConfig.enableAutoResize"
123+
/>
124+
<Label for="enable-auto-resize">{{ $t('compression_config.auto_resize') }}</Label>
125+
<Tooltip>
126+
<TooltipTrigger as-child>
127+
<div class="i-lucide-info cursor-help" />
128+
</TooltipTrigger>
129+
<TooltipContent>
130+
<p>{{ $t('compression_config.auto_resize_info') }}</p>
131+
</TooltipContent>
132+
</Tooltip>
133+
</div>
118134
<div class="flex gap-2 items-center">
119135
<Checkbox
120136
id="formats-thumbnail"

app/composables/useUploadConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ export interface compressFormat {
99

1010
export interface UploadConfig {
1111
enableCompression: boolean
12+
enableAutoResize: boolean
1213
formats: compressFormat
1314
}
1415

1516
export function useUploadConfig() {
1617
const config = useLocalStorage<UploadConfig>('upload-config', {
1718
enableCompression: true,
19+
enableAutoResize: true,
1820
formats: {
1921
jpeg: false,
2022
webp: true,

app/utils/compress.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@ import type { EncodeOptions as WebpOption } from '@jsquash/webp/meta'
66
interface Jpeg {
77
target: 'jpeg'
88
encodeOptions?: Partial<JpegOption>
9+
autoResize?: boolean
910
}
1011

1112
interface Webp {
1213
target: 'webp'
1314
encodeOptions?: Partial<WebpOption>
15+
autoResize?: boolean
1416
}
1517

1618
interface Avif {
1719
target: 'avif'
1820
encodeOptions?: Partial<AvifOption> & {
1921
bitDepth?: 8
2022
}
23+
autoResize?: boolean
2124
}
2225

2326
interface Thumbnail {

app/workers/encode.worker.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,45 @@ globalThis.onmessage = async (e) => {
1919

2020
try {
2121
let result: File | undefined
22+
let dataToEncode = imageData
23+
24+
// 自动缩放:短边 >= 2880 时缩放到短边 2160
25+
if (options.target !== 'thumbnail' && options.autoResize) {
26+
const minDimension = Math.min(imageData.width, imageData.height)
27+
if (minDimension >= 2880) {
28+
let targetWidth: number | undefined
29+
let targetHeight: number | undefined
30+
31+
if (imageData.width > imageData.height) {
32+
targetHeight = 2160
33+
targetWidth = Math.round(2160 * imageData.width / imageData.height)
34+
}
35+
else {
36+
targetWidth = 2160
37+
targetHeight = Math.round(2160 * imageData.height / imageData.width)
38+
}
39+
40+
dataToEncode = await resize(imageData, {
41+
width: targetWidth,
42+
height: targetHeight,
43+
})
44+
}
45+
}
2246

2347
if (options.target === 'jpeg') {
24-
const jpegData = await encodeJpeg(imageData, options.encodeOptions)
48+
const jpegData = await encodeJpeg(dataToEncode, options.encodeOptions)
2549
result = new File([jpegData], changeFileExtension(filename, 'jpg'), {
2650
type: 'image/jpeg',
2751
})
2852
}
2953
else if (options.target === 'webp') {
30-
const webpData = await encodeWebp(imageData, options.encodeOptions)
54+
const webpData = await encodeWebp(dataToEncode, options.encodeOptions)
3155
result = new File([webpData], changeFileExtension(filename, 'webp'), {
3256
type: 'image/webp',
3357
})
3458
}
3559
else if (options.target === 'avif') {
36-
const avifData = options.encodeOptions ? await encodeAvif(imageData, options.encodeOptions) : await encodeAvif(imageData)
60+
const avifData = options.encodeOptions ? await encodeAvif(dataToEncode, options.encodeOptions) : await encodeAvif(dataToEncode)
3761
result = new File([avifData], changeFileExtension(filename, 'avif'), {
3862
type: 'image/avif',
3963
})

i18n/locales/en.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ login_form:
4747
compression_config:
4848
title: Image Compression
4949
enable: Enable image compression
50-
info: Image compression can speed up page loading, but will slightly reduce image quality, and exif will be removed
50+
info: Image compression can speed up page loading, but will slightly reduce image quality, and file's exif will be removed
51+
auto_resize: Auto Resize
52+
auto_resize_info: Automatically resize images to 2160px on the short edge when it's 2880px or larger
5153
formats: Compression formats
5254
jpeg: JPEG
5355
webp: WebP

i18n/locales/zh.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ login_form:
4747
compression_config:
4848
title: 图片压缩
4949
enable: 启用图片压缩
50-
info: 图片压缩可以加快页面加载速度,但会略微降低图片质量,并且exif也会被移除
50+
info: 图片压缩可以加快页面加载速度,但会略微降低图片质量,并且文件的exif也会被移除
51+
auto_resize: 自动缩放
52+
auto_resize_info: 当图片短边大于等于2880像素时,自动缩放到短边2160像素
5153
formats: 压缩格式
5254
jpeg: JPEG
5355
webp: WebP

0 commit comments

Comments
 (0)