Skip to content

Commit

Permalink
✨ feat: 支持复制 Png 为高清图片
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Jun 3, 2021
1 parent 1a4ca47 commit 799008a
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 24 deletions.
8 changes: 7 additions & 1 deletion packages/image-gallery/src/utils/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* istanbul ignore file */

import { message } from 'antd';

/**
* 利用 Canvas 生成 png dataURL
* @param image
Expand All @@ -11,7 +13,11 @@ export const getImageBase64 = (image: HTMLImageElement, scale = 1) => {
canvas.height = image.height * scale;

const context = canvas.getContext('2d');
context?.drawImage(image, 0, 0);
context?.drawImage(image, 0, 0, image.width, image.height);

return canvas.toDataURL('image/png');
};

export const copySuccess = () => {
message.success('🎉 复制成功!');
};
19 changes: 13 additions & 6 deletions packages/image-gallery/src/utils/png.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
/* istanbul ignore file */

import { message } from 'antd';
import { getImageBase64 } from './helper';
import { copySuccess, getImageBase64 } from './helper';
import { copyToClipboard } from './clipboard';
import { svgSize } from './svg-size';

// 放大 8 倍
const SCALE = 8;

/**
* 复制 Png 到剪切板
* @param url
*/
export const copyPngFromSvg = async (url: string) => {
const res = await fetch(url);
const size = svgSize(await res.clone().text());

const svgBlob = await res.blob();
const svgUrl = URL.createObjectURL(svgBlob);

const image = new Image();
const image = new Image(size.width * SCALE, size.height * SCALE);
image.src = svgUrl;

image.onload = async () => {
const result = await fetch(getImageBase64(image));

// 如果浏览器支持 navigator.clipboard 接口
// 就使用 write 接口
const isSuccess = await copyToClipboard('image/png', await result.blob());

// 不然就用降级方案
if (!isSuccess) {
// 创建 image 对象
const img = document.createElement('img');
img.src = getImageBase64(image, 8);
img.src = getImageBase64(image, SCALE);
img.contentEditable = 'true';
document.body.appendChild(img);

Expand All @@ -39,7 +46,7 @@ export const copyPngFromSvg = async (url: string) => {
img.remove();
}

message.success('🎉 复制成功!');
copySuccess();
};
};

Expand All @@ -59,7 +66,7 @@ export const downloadPng = async (url: string, title: string) => {
image.onload = () => {
const a = document.createElement('a');
a.download = `${title}.png`;
a.href = getImageBase64(image, 8);
a.href = getImageBase64(image, SCALE);
a.click();
};
};
90 changes: 90 additions & 0 deletions packages/image-gallery/src/utils/svg-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* istanbul ignore file */

const SVG_HEADER_RE = /<svg\s[^>]+>/;
const SVG_WIDTH_RE = /[^-]\bwidth="([^%]+?)"|[^-]\bwidth='([^%]+?)'/;
const SVG_HEIGHT_RE = /\bheight="([^%]+?)"|\bheight='([^%]+?)'/;
const SVG_VIEWBOX_RE = /\bview[bB]ox="(.+?)"|\bview[bB]ox='(.+?)'/;
const SVG_UNITS_RE = /in$|mm$|cm$|pt$|pc$|px$|em$|ex$/;

// Filter NaN, Infinity, < 0
function isFinitePositive(val) {
return typeof val === 'number' && isFinite(val) && val > 0;
}

function svgAttrs(str) {
const width = str.match(SVG_WIDTH_RE);
const height = str.match(SVG_HEIGHT_RE);
const viewbox = str.match(SVG_VIEWBOX_RE);

return {
width: width && (width[1] || width[2]),
height: height && (height[1] || height[2]),
viewbox: viewbox && (viewbox[1] || viewbox[2]),
};
}

function units(str) {
if (!SVG_UNITS_RE.test(str)) return 'px';

return str.match(SVG_UNITS_RE)[0];
}

interface Size {
width: number;
height: number;
}

export const svgSize = (svgStr: string): Size => {
const attrs = svgAttrs(svgStr.match(SVG_HEADER_RE)[0]);
const width = parseFloat(attrs.width);
const height = parseFloat(attrs.height);

// Extract from direct values

if (attrs.width && attrs.height) {
if (!isFinitePositive(width) || !isFinitePositive(height)) return;

return {
width,
height,
};
}

// Extract from viewbox

const parts = (attrs.viewbox || '').split(' ');
const viewbox = {
width: parts[2],
height: parts[3],
};
const vbWidth = parseFloat(viewbox.width);
const vbHeight = parseFloat(viewbox.height);

if (!isFinitePositive(vbWidth) || !isFinitePositive(vbHeight)) return;
if (units(viewbox.width) !== units(viewbox.height)) return;

const ratio = vbWidth / vbHeight;

if (attrs.width) {
if (!isFinitePositive(width)) return;

return {
width,
height: width / ratio,
};
}

if (attrs.height) {
if (!isFinitePositive(height)) return;

return {
width: height * ratio,
height,
};
}

return {
width: vbWidth,
height: vbHeight,
};
};
36 changes: 19 additions & 17 deletions packages/image-gallery/src/utils/svg.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
/* istanbul ignore file */

// import { copyToClipboard } from './clipboard';
import { message } from 'antd';
import { copySuccess } from './helper';
import { copyToClipboard } from './clipboard';

/**
* 复制 SVG
* @param url
*/
export const copySVG = async (url: string) => {
const res = await fetch(url);
// const svgBlob = await res.text();
// console.log(svgBlob);
const svgBlob = await res.clone().blob();
// 如果浏览器支持 navigator.clipboard 接口
// 就使用 write 接口
// const isSuccess = await copyToClipboard('text/plain', new Blob([svgBlob], { type: 'text/plain' }));

// if (!isSuccess) {
const svgString = await res.text();
const input = document.createElement('input');
document.body.appendChild(input);
input.setAttribute('value', svgString);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
// }

message.success('🎉 复制成功!');
const isSuccess = await copyToClipboard(
'text/plain',
new Blob([svgBlob], { type: 'text/plain' }),
);

if (!isSuccess) {
const svgString = await res.text();
const input = document.createElement('input');
document.body.appendChild(input);
input.setAttribute('value', svgString);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
}

copySuccess();
};

/**
Expand Down

0 comments on commit 799008a

Please sign in to comment.