将 dataURL/Buffer 转换为 blob
Convert dataURL/Buffer to blob
我正在使用接受 Blob 格式的 npm canvas
package and I want to upload my canvas image data using an API,
最初我用它来将我的 dataUrl 格式转换为 blob:
const dataUrl = canvas.toDataURL();
console.log('[debug] dataUrl', dataUrl); // [debug] dataUrl data:image/png;base64,iVBORw0KGgoAAAAN......
...
...
const res = await fetch(dataUrl);
const blob = await res.blob();
console.log('blob', blob); // some Blob
我将框架迁移到 remix.js,当我尝试 运行 这段代码时,出现错误:
TypeError: Only HTTP(S) protocols are supported
Canvas 可以将图像导出为 dataURL 或 Buffer,但我似乎无法弄清楚如何将它们中的任何一个转换为 Blob,任何帮助将不胜感激
Buffer -> <Buffer 66 00 68 00 71 00 77 00 68 00 67 00 61 00 64 00 73 00>
DataURL -> data:image/png;base64,iVBORw0KGgoAAAAN......
这是一个完整的示例,说明如何从 canvas
获取 Pinata 文件上传结果。我已经包含了很多评论,如果有不清楚的地方可以进一步解释:
文件:
./package.json
:
{
"name": "so-71458029",
"version": "0.1.0",
"description": "",
"type": "module",
"scripts": {
"compile": "tsc",
"dev": "ts-node --esm src/main.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.21",
"ts-node": "^10.7.0",
"typescript": "^4.6.2"
},
"dependencies": {
"canvas": "^2.9.0",
"dotenv": "^16.0.0",
"form-data": "^4.0.0",
"node-fetch": "^3.2.3"
}
}
./tsconfig.json
:
{
"compilerOptions": {
// Node 16 ESM options
// Ref: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping#node-16
"lib": ["es2021"],
"module": "esnext",
"moduleResolution": "node",
"target": "es2021",
// Compatibility
"allowSyntheticDefaultImports": true,
// Suggestion: strict options for safety
"strict": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"useUnknownInCatchVariables": true,
// output config
"declaration": true,
"outDir": "dist",
},
"include": [
"./src/**/*"
]
}
./.env
:
PINATA_JWT=your_actual_pinata_jwt
./src/canvas.ts
:
/*
import {createCanvas, type Canvas} from 'canvas';
^^^^^^^^^^^^
SyntaxError: Named export 'createCanvas' not found.
The requested module 'canvas' is a CommonJS module,
which may not support all module.exports as named exports.
*/
import _canvas, {type Canvas} from 'canvas';
import FormData from 'form-data';
import {Headers} from 'node-fetch';
const {createCanvas} = _canvas;
/** https://github.com/form-data/form-data/tree/v4.0.0#usage */
export function createRequestInitFromCanvas (canvas: Canvas): {
body: FormData;
headers: Headers;
} {
const form = new FormData();
// Stream the canvas image data. (Other formats available.)
// https://github.com/Automattic/node-canvas/tree/v2.9.0#canvascreatepngstream
const pngStream = canvas.createPNGStream();
const fileOptions: FormData.AppendOptions = {
contentType: 'image/png',
filename: 'canvas.png',
};
form.append('file', pngStream, fileOptions);
return {
body: form,
headers: new Headers(form.getHeaders()),
};
}
/** https://github.com/Automattic/node-canvas/tree/v2.9.0#quick-example */
export function getExampleCanvas (): Canvas {
const canvas = createCanvas(200, 200);
const ctx = canvas.getContext('2d');
// Write "Awesome!"
ctx.font = '30px Impact';
ctx.rotate(0.1);
ctx.fillText('Awesome!', 50, 100);
// Draw line under text
const text = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.lineTo(50, 102);
ctx.lineTo(50 + text.width, 102);
ctx.stroke();
return canvas;
}
./src/main.ts
:
import 'dotenv/config';
import assert from 'assert/strict';
import {type Canvas} from 'canvas';
import {default as fetch, Request, Response} from 'node-fetch';
import {createRequestInitFromCanvas, getExampleCanvas} from './canvas.js';
async function createResponseError (response: Response): Promise<Error> {
let body: any = '';
try {
body = await response.text();
body = JSON.parse(body);
}
catch {/* empty */}
if (!body) body = null;
const json = JSON.stringify({
status: response.status,
headers: Object.fromEntries([...response.headers].sort()),
body,
}, null, 2);
return new Error(`Response error:\n${json}`);
}
/** https://docs.pinata.cloud/api-pinning/pin-file#response */
type PinataFileUploadResponse = {
IpfsHash: string;
PinSize: number;
/** ISO 8601 datetime */
Timestamp: string;
};
/** https://docs.pinata.cloud/api-pinning/pin-file */
async function pinCanvasImage (pinataJWT: string, canvas: Canvas): Promise<PinataFileUploadResponse> {
const {body, headers} = createRequestInitFromCanvas(canvas);
// https://docs.pinata.cloud/#connecting-to-the-api
// https://docs.pinata.cloud/#your-api-keys
headers.set('Authorization', `Bearer ${pinataJWT}`);
// See more body options and examples at:
// https://docs.pinata.cloud/api-pinning/pin-file#body
// https://docs.pinata.cloud/api-pinning/pin-file#pinataoptions-optional
// const pinataOptions = {};
// body.append('pinataOptions', JSON.stringify(pinataOptions));
// https://docs.pinata.cloud/api-pinning/pin-file#pinatametadata-optional
// const pinataMetadata = {};
// body.append('pinataMetadata', JSON.stringify(pinataMetadata));
const request = new Request('https://api.pinata.cloud/pinning/pinFileToIPFS', {
method: 'POST',
body,
headers,
});
const response = await fetch(request);
if (!response.ok) {
const err = await createResponseError(response);
throw err;
}
return response.json() as Promise<PinataFileUploadResponse>;
}
async function main () {
const {PINATA_JWT} = process.env;
// Ensure that the environment variable exists
// https://nodejs.org/api/assert.html#assertokvalue-message
assert.ok(PINATA_JWT);
const canvas = getExampleCanvas();
const result = await pinCanvasImage(PINATA_JWT, canvas);
console.log(result);
}
main();
运行它::
Node/npm 版本:
$ node --version
v16.14.0
$ npm --version
8.3.1
运行 使用:
$ npm install
$ npm run dev
对于正在寻找 axios 版本答案的任何人,可以在此处找到 form-data 与 axios 的用法:https://www.npmjs.com/package/form-data
interface PinataMetadata {
IpfsHash: string;
PinSize: number;
Timestamp: string | number;
}
const PinataInstance = axios.create({
baseURL: 'https://api.pinata.cloud',
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret
}
});
export const pinFileToIPFS = async (pngStream: PNGStream) => {
const path = '/pinning/pinFileToIPFS';
const form = new FormData();
const fileOptions: FormData.AppendOptions = {
contentType: 'image/png',
filename: 'canvas.png'
};
form.append('file', pngStream, fileOptions);
try {
const res = await PinataInstance.post<PinataMetadata>(path, form, {
headers: { ...form.getHeaders() }
});
return 'https://gateway.pinata.cloud/ipfs/' + res.data.IpfsHash;
} catch (error: any) {
console.error('Pinata error: ', error);
}
};
我正在使用接受 Blob 格式的 npm canvas
package and I want to upload my canvas image data using an API,
最初我用它来将我的 dataUrl 格式转换为 blob:
const dataUrl = canvas.toDataURL();
console.log('[debug] dataUrl', dataUrl); // [debug] dataUrl data:image/png;base64,iVBORw0KGgoAAAAN......
...
...
const res = await fetch(dataUrl);
const blob = await res.blob();
console.log('blob', blob); // some Blob
我将框架迁移到 remix.js,当我尝试 运行 这段代码时,出现错误:
TypeError: Only HTTP(S) protocols are supported
Canvas 可以将图像导出为 dataURL 或 Buffer,但我似乎无法弄清楚如何将它们中的任何一个转换为 Blob,任何帮助将不胜感激
Buffer -> <Buffer 66 00 68 00 71 00 77 00 68 00 67 00 61 00 64 00 73 00>
DataURL -> data:image/png;base64,iVBORw0KGgoAAAAN......
这是一个完整的示例,说明如何从 canvas
获取 Pinata 文件上传结果。我已经包含了很多评论,如果有不清楚的地方可以进一步解释:
文件:
./package.json
:
{
"name": "so-71458029",
"version": "0.1.0",
"description": "",
"type": "module",
"scripts": {
"compile": "tsc",
"dev": "ts-node --esm src/main.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^17.0.21",
"ts-node": "^10.7.0",
"typescript": "^4.6.2"
},
"dependencies": {
"canvas": "^2.9.0",
"dotenv": "^16.0.0",
"form-data": "^4.0.0",
"node-fetch": "^3.2.3"
}
}
./tsconfig.json
:
{
"compilerOptions": {
// Node 16 ESM options
// Ref: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping#node-16
"lib": ["es2021"],
"module": "esnext",
"moduleResolution": "node",
"target": "es2021",
// Compatibility
"allowSyntheticDefaultImports": true,
// Suggestion: strict options for safety
"strict": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"useUnknownInCatchVariables": true,
// output config
"declaration": true,
"outDir": "dist",
},
"include": [
"./src/**/*"
]
}
./.env
:
PINATA_JWT=your_actual_pinata_jwt
./src/canvas.ts
:
/*
import {createCanvas, type Canvas} from 'canvas';
^^^^^^^^^^^^
SyntaxError: Named export 'createCanvas' not found.
The requested module 'canvas' is a CommonJS module,
which may not support all module.exports as named exports.
*/
import _canvas, {type Canvas} from 'canvas';
import FormData from 'form-data';
import {Headers} from 'node-fetch';
const {createCanvas} = _canvas;
/** https://github.com/form-data/form-data/tree/v4.0.0#usage */
export function createRequestInitFromCanvas (canvas: Canvas): {
body: FormData;
headers: Headers;
} {
const form = new FormData();
// Stream the canvas image data. (Other formats available.)
// https://github.com/Automattic/node-canvas/tree/v2.9.0#canvascreatepngstream
const pngStream = canvas.createPNGStream();
const fileOptions: FormData.AppendOptions = {
contentType: 'image/png',
filename: 'canvas.png',
};
form.append('file', pngStream, fileOptions);
return {
body: form,
headers: new Headers(form.getHeaders()),
};
}
/** https://github.com/Automattic/node-canvas/tree/v2.9.0#quick-example */
export function getExampleCanvas (): Canvas {
const canvas = createCanvas(200, 200);
const ctx = canvas.getContext('2d');
// Write "Awesome!"
ctx.font = '30px Impact';
ctx.rotate(0.1);
ctx.fillText('Awesome!', 50, 100);
// Draw line under text
const text = ctx.measureText('Awesome!');
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.beginPath();
ctx.lineTo(50, 102);
ctx.lineTo(50 + text.width, 102);
ctx.stroke();
return canvas;
}
./src/main.ts
:
import 'dotenv/config';
import assert from 'assert/strict';
import {type Canvas} from 'canvas';
import {default as fetch, Request, Response} from 'node-fetch';
import {createRequestInitFromCanvas, getExampleCanvas} from './canvas.js';
async function createResponseError (response: Response): Promise<Error> {
let body: any = '';
try {
body = await response.text();
body = JSON.parse(body);
}
catch {/* empty */}
if (!body) body = null;
const json = JSON.stringify({
status: response.status,
headers: Object.fromEntries([...response.headers].sort()),
body,
}, null, 2);
return new Error(`Response error:\n${json}`);
}
/** https://docs.pinata.cloud/api-pinning/pin-file#response */
type PinataFileUploadResponse = {
IpfsHash: string;
PinSize: number;
/** ISO 8601 datetime */
Timestamp: string;
};
/** https://docs.pinata.cloud/api-pinning/pin-file */
async function pinCanvasImage (pinataJWT: string, canvas: Canvas): Promise<PinataFileUploadResponse> {
const {body, headers} = createRequestInitFromCanvas(canvas);
// https://docs.pinata.cloud/#connecting-to-the-api
// https://docs.pinata.cloud/#your-api-keys
headers.set('Authorization', `Bearer ${pinataJWT}`);
// See more body options and examples at:
// https://docs.pinata.cloud/api-pinning/pin-file#body
// https://docs.pinata.cloud/api-pinning/pin-file#pinataoptions-optional
// const pinataOptions = {};
// body.append('pinataOptions', JSON.stringify(pinataOptions));
// https://docs.pinata.cloud/api-pinning/pin-file#pinatametadata-optional
// const pinataMetadata = {};
// body.append('pinataMetadata', JSON.stringify(pinataMetadata));
const request = new Request('https://api.pinata.cloud/pinning/pinFileToIPFS', {
method: 'POST',
body,
headers,
});
const response = await fetch(request);
if (!response.ok) {
const err = await createResponseError(response);
throw err;
}
return response.json() as Promise<PinataFileUploadResponse>;
}
async function main () {
const {PINATA_JWT} = process.env;
// Ensure that the environment variable exists
// https://nodejs.org/api/assert.html#assertokvalue-message
assert.ok(PINATA_JWT);
const canvas = getExampleCanvas();
const result = await pinCanvasImage(PINATA_JWT, canvas);
console.log(result);
}
main();
运行它::
Node/npm 版本:
$ node --version
v16.14.0
$ npm --version
8.3.1
运行 使用:
$ npm install
$ npm run dev
对于正在寻找 axios 版本答案的任何人,可以在此处找到 form-data 与 axios 的用法:https://www.npmjs.com/package/form-data
interface PinataMetadata {
IpfsHash: string;
PinSize: number;
Timestamp: string | number;
}
const PinataInstance = axios.create({
baseURL: 'https://api.pinata.cloud',
headers: {
pinata_api_key: key,
pinata_secret_api_key: secret
}
});
export const pinFileToIPFS = async (pngStream: PNGStream) => {
const path = '/pinning/pinFileToIPFS';
const form = new FormData();
const fileOptions: FormData.AppendOptions = {
contentType: 'image/png',
filename: 'canvas.png'
};
form.append('file', pngStream, fileOptions);
try {
const res = await PinataInstance.post<PinataMetadata>(path, form, {
headers: { ...form.getHeaders() }
});
return 'https://gateway.pinata.cloud/ipfs/' + res.data.IpfsHash;
} catch (error: any) {
console.error('Pinata error: ', error);
}
};