Skip to content

Commit

Permalink
feat: Add browser-image-compression and compressorjs dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
ATQQ committed Mar 30, 2024
1 parent 61af4bb commit 105e594
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 111 deletions.
2 changes: 2 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
"@element-plus/icons-vue": "^2.3.1",
"@vueuse/core": "^10.9.0",
"axios": "^0.27.2",
"browser-image-compression": "^2.0.2",
"clipboard-copy": "^4.0.1",
"compressorjs": "^1.2.1",
"element-plus": "^2.6.3",
"pinia": "^2.1.7",
"qiniu": "^7.11.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/components/ImageList.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script setup lang="ts">
import { copyRes, formatDate, formatSize } from '../utils/stringUtil';
import { copyRes, formatDate } from '../utils/stringUtil';
import { Picture } from '@element-plus/icons-vue'
import { useImageStore } from '@/store'
import { computed } from 'vue';
import { ElMessageBox } from 'element-plus'
import { IImage } from '@/store/modules/imageStore'
import { ref } from 'vue'
import { useUploadConfig } from '@/composables';
import { calculateCompressionPercentage } from '@/utils/file';
import { calculateCompressionPercentage, formatSize } from '@/utils';
const imageStore = useImageStore()
const copyAddress = (url: string) => {
copyRes(url)
Expand Down
22 changes: 22 additions & 0 deletions packages/client/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface CompressOptions {
/**
* 压缩质量(0-100)
* @default 80
*/
quality?: number
/**
* 压缩后更大是否使用原图
* @default true
*/
noCompressIfLarger?: boolean
/**
* 压缩后的新宽度
* @default 原尺寸
*/
width?: number
/**
* 压缩后新高度
* @default 原尺寸
*/
height?: number
}
44 changes: 9 additions & 35 deletions packages/client/src/utils/file.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
// @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
}
import { compressJPGImage, isJPG } from './index'
import type { CompressOptions } from '@/types'

async function compressImage(file: File, ops: CompressOptions = {}) {
const { width, height, quality = 80, noCompressIfLarger = true } = ops
const isPng = await isPNG(file)
let newFile: File | null = null
const isJpg = await isJPG(file)

let newFile: Blob | 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 (isJpg) {
newFile = await compressJPGImage(file, 'browser-image-compression', ops)
}

if (!newFile) {
return file
Expand All @@ -45,17 +31,6 @@ async function compressImage(file: File, ops: CompressOptions = {}) {
return file.size > newFile.size ? newFile : file
}

/**
* 计算压缩比例
*/
function calculateCompressionPercentage(originalSize: number, compressedSize: number) {
if (originalSize === 0) {
return 0
}
const percentageDecreased = ((originalSize - compressedSize) / originalSize) * 100
return percentageDecreased.toFixed(2) // Returns the percentage with 2 decimal places
}

function getBlobArrayBuffer(file: Blob): Promise<ArrayBuffer> {
return file.arrayBuffer()
}
Expand Down Expand Up @@ -102,5 +77,4 @@ function convertQualityToBit(quality: number): number {
export {
compressImage,
getImageDimensions,
calculateCompressionPercentage,
}
102 changes: 102 additions & 0 deletions packages/client/src/utils/image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import imageCompression from 'browser-image-compression'
import Compressor from 'compressorjs'
import { createObjectURL, dataURItoFile } from './transform'
import type { CompressOptions } from '@/types'

export async function isJPG(file: File) {
// 提取前3个字节
const arraybuffer = await file.slice(0, 3).arrayBuffer()

// JPG 的前3字节16进制表示
const signature = [0xFF, 0xD8, 0xFF]
// 转为 8位无符号整数数组 方便对比
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
const source = new Uint8Array(arraybuffer)

// 逐个字节对比
return source.every((value, index) => value === signature[index])
}
export async function compressJPGImage(file: File, method: string, ops: CompressOptions = {}) {
let newFile: Blob = file
const { noCompressIfLarger = true } = ops
if (method === 'canvas') {
newFile = await compressImageByCanvas(file, ops)
}
if (method === 'browser-image-compression') {
newFile = await compressImageByImageCompression(file, ops)
}
if (method === 'Compressor') {
newFile = await compressImageByCompressor(file, ops)
}

if (!noCompressIfLarger) {
return newFile
}

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

export async function compressImageByCanvas(file: File, options: CompressOptions = {}) {
const { quality = 80 } = options
let { width, height } = options

let _resolve: any, _reject
const promise = new Promise<File>((resolve, reject) => {
_resolve = resolve
_reject = reject
})

const img = new Image()
img.onload = function () {
// 如果只指定了宽度或高度,则另一个按比例缩放
if (width && !height) {
height = Math.round(img.height * (width / img.width))
}
else if (!width && height) {
width = Math.round(img.width * (height / img.height))
}

const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')

// 设置 canvas 的宽高与图片一致
canvas.width = width || img.width
canvas.height = height || img.height

// 在 canvas 上绘制图片
ctx?.drawImage(img, 0, 0, canvas.width, canvas.height)

// 获取压缩后的图片数据
const compressedDataUrl = canvas.toDataURL('image/jpeg', quality / 100)
_resolve(dataURItoFile(compressedDataUrl, file.name))
}

img.src = createObjectURL(file)
return promise
}

export function compressImageByImageCompression(file: File, options: CompressOptions = {}) {
const { width, height, quality = 80 } = options
return imageCompression(file, {
maxSizeMB: Math.round(file.size / (1024 * 1024) * quality / 100),
maxWidthOrHeight: width || height || undefined,
libURL: 'https://cdn.staticfile.net/browser-image-compression/2.0.2/browser-image-compression.js',
})
}

export async function compressImageByCompressor(file: File, options: CompressOptions = {}) {
const { width, height, quality = 80 } = options
return new Promise<File | Blob>((resolve, reject) => {
return new Compressor(file, {
quality: quality / 100,
width: width || undefined,
height: height || undefined,
success(result) {
resolve(result)
},
error(err) {
reject(err)
},
})
})
}
3 changes: 3 additions & 0 deletions packages/client/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './transform'

export * from './image'
18 changes: 0 additions & 18 deletions packages/client/src/utils/stringUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,3 @@ export function getFileMd5Hash(file: File) {
loadNext()
})
}

export function formatSize(
size: number,
pointLength?: number,
units?: string[],
) {
let unit: any
units = units || ['B', 'K', 'M', 'G', 'TB']
// eslint-disable-next-line no-cond-assign
while ((unit = units.shift()) && size > 1024) {
size /= 1024
}
return (
(unit === 'B'
? size
: size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit
)
}
49 changes: 49 additions & 0 deletions packages/client/src/utils/transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export function dataURItoFile(dataURI: string, fileName: string) {
const [dataDescription, base64Data] = dataURI.split(',')
// 文件类型
const mimetype = dataDescription.match(/:(.*?);/)?.[1]

// 解码 base64 数据
const decodedData = atob(base64Data)
let n = decodedData.length
const u8arr = new Uint8Array(n)

while (n--) {
u8arr[n] = decodedData.charCodeAt(n)
}

return new File([u8arr], fileName, { type: mimetype })
}

export function formatSize(
size: number,
pointLength?: number,
units?: string[],
) {
let unit: any
units = units || ['B', 'K', 'M', 'G', 'TB']
// eslint-disable-next-line no-cond-assign
while ((unit = units.shift()) && size > 1024) {
size /= 1024
}
return (
(unit === 'B'
? size
: size.toFixed(pointLength === undefined ? 2 : pointLength)) + unit
)
}

export function createObjectURL(file: Blob) {
return URL.createObjectURL(file)
}

/**
* 计算压缩比例
*/
export function calculateCompressionPercentage(originalSize: number, compressedSize: number) {
if (originalSize === 0) {
return 0
}
const percentageDecreased = ((originalSize - compressedSize) / originalSize) * 100
return percentageDecreased.toFixed(2) // Returns the percentage with 2 decimal places
}
Loading

0 comments on commit 105e594

Please sign in to comment.