Skip to content

Commit

Permalink
feat: 基于UPNG支持png 图片压缩
Browse files Browse the repository at this point in the history
  • Loading branch information
ATQQ committed Mar 9, 2024
1 parent d8ec8dd commit da4fc64
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 1,277 deletions.
3 changes: 0 additions & 3 deletions packages/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
<title>七牛云OSS图床 | 粥里有勺糖</title>
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({ id: "KK6tcRYp7qnv7PCY", ck: "KK6tcRYp7qnv7PCY", hashMode: true })</script>
<script
src="https://polyfill.alicdn.com/polyfill.min.js?features=es2019%2Ces2018%2Ces2017%2Ces5%2Ces6%2Ces7%2Cdefault"></script>
<script src="/UPNG.js"></script>
</head>

<body>
Expand Down
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"qiniu": "^7.11.0",
"qiniu-js": "^3.4.1",
"spark-md5": "^3.0.2",
"upng-js": "^2.1.0",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
Expand Down
1,225 changes: 0 additions & 1,225 deletions packages/client/public/UPNG.js

This file was deleted.

9 changes: 6 additions & 3 deletions packages/client/src/components/ImageList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const checkInfo = (image: IImage) => {
<li>链接:<a target="_blank" href=${image.url}>${image.url}</a></li>
<li>上传时间:${image.date && formatDate(image.date)}</li>
<li>大小:${image.size ? formatSize(image.size) : '未知'}</li>
${image.originSize ? `<li>压缩前大小:${formatSize(image.originSize)}</li>` : ''}
</ul>
</div>`
})
Expand Down Expand Up @@ -56,7 +57,8 @@ const showImage = computed(() => {
</a>
</span>
<span style="width: 160px;" class="right">
<el-button v-if="image.size" type="warning" link>{{ formatSize(image.size) }}</el-button>
<el-button v-if="image.size" :type="image.originSize ? 'success' : 'warning'" link>{{ formatSize(image.size)
}}</el-button>
<el-button type="primary" link @click="checkInfo(image)">🔍</el-button>
<el-button type="primary" link @click="copyAddress(image.url)">url</el-button>
<el-button type="success" link @click="copyMdAddress(image.url)">markdown</el-button>
Expand Down Expand Up @@ -85,7 +87,7 @@ ul.el-upload-list {
display: flex;
justify-content: space-between;
.left{
.left {
flex: 1;
}
Expand Down Expand Up @@ -127,4 +129,5 @@ ul.el-upload-list {
li {
word-break: break-all;
}
}</style>
}
</style>
22 changes: 10 additions & 12 deletions packages/client/src/components/ImageUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import { UploadFilled } from '@element-plus/icons-vue'
import { computed, ref, watch } from 'vue';
import { ElMessage, type UploadInstance, type UploadProps, type UploadUserFile } from 'element-plus'
import { compressImage, uploadFile } from '../utils/qiniu'
import { uploadFile } from '../utils/qiniu'
import { useFocus } from '@vueuse/core';
import { useConfigStore, useImageStore } from '@/store'
import { storeToRefs } from 'pinia';
import { formatSize } from '@/utils/stringUtil';
import { useUploadConfig } from '@/composables';
import { compressImage } from '@/utils/file';
const imageStore = useImageStore()
const configStore = useConfigStore()
Expand Down Expand Up @@ -35,17 +35,14 @@ watch(files, async () => {
if (!file.raw) {
continue
}
let fileRaw = file.raw
if (cacheConfig.value.compressImage) {
// 采取自动压缩策略(TODO: 未来开放自定义调整)
compressImage(file.raw, {
noCompressIfLarger: true,
quality: 0.5
}).then(v => {
console.log('origin', formatSize(file.raw!.size), 'result', formatSize(v.dist.size));
})
// TODO: 未来开放自定义调整
// 采取自动压缩策略
fileRaw = await compressImage(file.raw) as any
}
uploadFile(file.raw, qiniu.value, {
uploadFile(fileRaw, qiniu.value, {
process(percent) {
file.percentage = percent
if (percent === 100) {
Expand All @@ -61,8 +58,9 @@ watch(files, async () => {
imageStore.addImage({
url: v,
name: file.name || 'image',
file: file.raw,
size: file.raw?.size || 0,
file: fileRaw,
size: fileRaw?.size || 0,
originSize: fileRaw === file.raw? 0 : file.raw?.size,
})
}).catch(err => {
ElMessage.error(err)
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/components/UploadTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ watch(() => success.value.length, () => {
<template>
<div class="tool-wrapper">
<span class="autoCopy">
<el-switch v-model="cacheConfig.autoCopy" inline-prompt active-text="自动复制" inactive-text="关闭自动复制" />
<el-switch v-model="cacheConfig.autoCopy" inline-prompt active-text="自动复制" inactive-text="自动复制" />
<el-select style="width: 100px;" v-if="cacheConfig.autoCopy" v-model="cacheConfig.copyType"
placeholder="选择复制类型" size="small">
<el-option label="链接" value="url" />
Expand All @@ -30,8 +30,8 @@ watch(() => success.value.length, () => {
</el-select>
</span>
<span class="compress">
<el-switch v-model="cacheConfig.compressImage" inline-prompt active-text="压缩" inactive-text="关闭压缩" />
<el-switch v-if="cacheConfig.compressImage" class="preview-switch" v-model="cacheConfig.compressPreview" inline-prompt active-text="预览" inactive-text="关闭预览" />
<el-switch v-model="cacheConfig.compressImage" inline-prompt active-text="压缩" inactive-text="压缩" />
<!-- <el-switch v-if="cacheConfig.compressImage" class="preview-switch" v-model="cacheConfig.compressPreview" inline-prompt active-text="预览" inactive-text="关闭预览" /> -->
</span>
</div>
</template>
Expand Down
6 changes: 3 additions & 3 deletions packages/client/src/shims-vue.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// 第三方库的类型定义
interface Window {
UPNG: any
}
// interface Window {
// UPNG: any
// }
9 changes: 8 additions & 1 deletion packages/client/src/store/modules/imageStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { defineStore } from 'pinia'

export interface IImage { url: string, name: string, file?: File, date?: number, size: number }
export interface IImage {
url: string
name: string
file?: File
date?: number
size: number
originSize?: number
}

const imgStore = defineStore('imgStore', {
state: () => ({
Expand Down
94 changes: 94 additions & 0 deletions packages/client/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// @ts-expect-error
import UPNG from 'upng-js'
interface CompressOptions {
/**
* 压缩质量(0-100)
* @default 80
*/
quality?: number
/**
* 压缩后更大是否使用原图
* @default true
*/
noCompressIfLarger?: boolean
/**
* 压缩后的新宽度
* @default 原尺寸
*/
width?: number
/**
* 压缩后新高度
* @default 原尺寸
*/
height?: number
}
async function compressImage(file: File, ops: CompressOptions = {}) {
const { width, height, quality = 80, noCompressIfLarger } = ops
const isPng = await isPNG(file)
let newFile: File | null = null
if (isPng) {
const arrayBuffer = await getBlobArrayBuffer(file)
const decoded = UPNG.decode(arrayBuffer)
const rgba8 = UPNG.toRGBA8(decoded)
const compressed = UPNG.encode(rgba8, width || decoded.width, height || decoded.height, convertQualityToBit(quality))
newFile = new File([compressed], file.name, { type: 'image/png' })
}

if (!newFile) {
return file
}

if (!noCompressIfLarger) {
return newFile
}

return file.size > newFile.size ? newFile : file
}

function getBlobArrayBuffer(file: Blob): Promise<ArrayBuffer> {
return file.arrayBuffer()
}

async function isPNG(file: File) {
const arraybuffer = await getBlobArrayBuffer(file.slice(0, 8))
return signatureEqual(arraybuffer, [137, 80, 78, 71, 13, 10, 26, 10])
}

function signatureEqual(source: ArrayBuffer, signature: number[]) {
const array = new Uint8Array(source)
for (let i = 0; i < signature.length; i++) {
if (array[i] !== signature[i]) {
return false
}
}
return true
}

function getImageDimensions(file: File): Promise<{ width: number, height: number }> {
return new Promise ((resolve) => {
const img = new Image()
img.onload = function () {
resolve({ width: img.width, height: img.height })
}
img.onerror = function () {
resolve({ width: 0, height: 0 })
}
img.src = URL.createObjectURL(file)
})
}

function convertQualityToBit(quality: number): number {
let bit = 0
if (quality > 100 || quality < 0) {
bit = 0
}
else {
bit = !quality ? 0 : quality * 256 * 0.01
}
return bit
}

export {
compressImage,
getImageDimensions,
}
27 changes: 0 additions & 27 deletions packages/client/src/utils/qiniu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,34 +49,7 @@ async function generateNewFileKey(file: File, prefix = 'mdImg', scope = 'sugar')
return `${prefix}/${scope}/${md5}`
}

interface CompressOptions {
/**
* 压缩质量
*/
quality?: number
/**
* 压缩后更大是否使用原图
*/
noCompressIfLarger?: boolean
/**
* 压缩后的新宽度
*/
width?: number
/**
* 压缩后新高度
*/
height?: number
}

async function compressImage(file: File, ops: CompressOptions) {
const { type, size } = file
// 根据类型选择不同的压缩工具
if (type === 'image/png') {
// window.UPNG
}
}
export {
uploadFile,
generateNewFileKey,
compressImage,
}
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit da4fc64

Please sign in to comment.