fix: resolve copy image failure for JPEG format pictures (#11529)
- Convert all image formats to PNG before writing to clipboard to ensure compatibility - Refactor handleCopyImage to unify image source handling (Base64, File, URL) - Add convertImageToPng utility function using canvas API for robust conversion - Remove fallback logic that attempted to write unsupported JPEG format
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
} from '@ant-design/icons'
|
||||
import { loggerService } from '@logger'
|
||||
import { download } from '@renderer/utils/download'
|
||||
import { convertImageToPng } from '@renderer/utils/image'
|
||||
import type { ImageProps as AntImageProps } from 'antd'
|
||||
import { Dropdown, Image as AntImage, Space } from 'antd'
|
||||
import { Base64 } from 'js-base64'
|
||||
@@ -33,39 +34,38 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
|
||||
// 复制图片到剪贴板
|
||||
const handleCopyImage = async (src: string) => {
|
||||
try {
|
||||
let blob: Blob
|
||||
|
||||
if (src.startsWith('data:')) {
|
||||
// 处理 base64 格式的图片
|
||||
const match = src.match(/^data:(image\/\w+);base64,(.+)$/)
|
||||
if (!match) throw new Error('Invalid base64 image format')
|
||||
const mimeType = match[1]
|
||||
const byteArray = Base64.toUint8Array(match[2])
|
||||
const blob = new Blob([byteArray], { type: mimeType })
|
||||
await navigator.clipboard.write([new ClipboardItem({ [mimeType]: blob })])
|
||||
blob = new Blob([byteArray], { type: mimeType })
|
||||
} else if (src.startsWith('file://')) {
|
||||
// 处理本地文件路径
|
||||
const bytes = await window.api.fs.read(src)
|
||||
const mimeType = mime.getType(src) || 'application/octet-stream'
|
||||
const blob = new Blob([bytes], { type: mimeType })
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
[mimeType]: blob
|
||||
})
|
||||
])
|
||||
blob = new Blob([bytes], { type: mimeType })
|
||||
} else {
|
||||
// 处理 URL 格式的图片
|
||||
const response = await fetch(src)
|
||||
const blob = await response.blob()
|
||||
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob
|
||||
})
|
||||
])
|
||||
blob = await response.blob()
|
||||
}
|
||||
|
||||
// 统一转换为 PNG 以确保兼容性(剪贴板 API 不支持 JPEG)
|
||||
const pngBlob = await convertImageToPng(blob)
|
||||
|
||||
const item = new ClipboardItem({
|
||||
'image/png': pngBlob
|
||||
})
|
||||
await navigator.clipboard.write([item])
|
||||
|
||||
window.toast.success(t('message.copy.success'))
|
||||
} catch (error) {
|
||||
logger.error('Failed to copy image:', error as Error)
|
||||
const err = error as Error
|
||||
logger.error(`Failed to copy image: ${err.message}`, { stack: err.stack })
|
||||
window.toast.error(t('message.copy.failed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,3 +566,54 @@ export const makeSvgSizeAdaptive = (element: Element): Element => {
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片 Blob 转换为 PNG 格式的 Blob
|
||||
* @param blob 原始图片 Blob
|
||||
* @returns Promise<Blob> 转换后的 PNG Blob
|
||||
*/
|
||||
export const convertImageToPng = async (blob: Blob): Promise<Blob> => {
|
||||
if (blob.type === 'image/png') {
|
||||
return blob
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
if (!ctx) {
|
||||
URL.revokeObjectURL(url)
|
||||
reject(new Error('Failed to get canvas context'))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.drawImage(img, 0, 0)
|
||||
canvas.toBlob((pngBlob) => {
|
||||
URL.revokeObjectURL(url)
|
||||
if (pngBlob) {
|
||||
resolve(pngBlob)
|
||||
} else {
|
||||
reject(new Error('Failed to convert image to png'))
|
||||
}
|
||||
}, 'image/png')
|
||||
} catch (error) {
|
||||
URL.revokeObjectURL(url)
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url)
|
||||
reject(new Error('Failed to load image for conversion'))
|
||||
}
|
||||
|
||||
img.src = url
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user