0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 05:33:02 -05:00

Add image library to optimize performance (#133)

* Add image library to optimize performance

* fix and improve

* Add changelog and improve naming

* refactor

* improve

* fix todos
This commit is contained in:
Jordi Sala Morales 2024-06-03 17:29:33 +02:00 committed by GitHub
parent a4113382bb
commit f726dc9cec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 122 additions and 32 deletions

View file

@ -0,0 +1,5 @@
---
"penpot-exporter": patch
---
Optimize image exporting when there are multiple copies of the same image in the file

View file

@ -0,0 +1,23 @@
import { ImageColor } from '@ui/lib/types/utils/imageColor';
class ImageLibrary {
private images: Record<string, ImageColor> = {};
public register(hash: string, image: ImageColor) {
this.images[hash] = image;
}
public get(hash: string): ImageColor | undefined {
return this.images[hash];
}
public all(): Record<string, ImageColor> {
return this.images;
}
public init(images: Record<string, ImageColor>): void {
this.images = images;
}
}
export const imagesLibrary = new ImageLibrary();

View file

@ -1,4 +1,5 @@
import { componentsLibrary } from '@plugin/ComponentLibrary';
import { imagesLibrary } from '@plugin/ImageLibrary';
import { PenpotDocument } from '@ui/types';
@ -27,6 +28,7 @@ export const transformDocumentNode = async (node: DocumentNode): Promise<PenpotD
return {
name: node.name,
children,
components: componentsLibrary.all()
components: componentsLibrary.all(),
images: imagesLibrary.all()
};
};

View file

@ -1,3 +1,4 @@
import { imagesLibrary } from '@plugin/ImageLibrary';
import { detectMimeType } from '@plugin/utils';
import { Fill } from '@ui/lib/types/utils/fill';
@ -9,28 +10,46 @@ export const translateImageFill = async (fill: ImagePaint): Promise<Fill | undef
return {
fillOpacity: !fill.visible ? 0 : fill.opacity,
fillImage: fillImage
fillImage
};
};
const translateImage = async (imageHash: string | null): Promise<ImageColor | undefined> => {
if (!imageHash) return;
const imageColor = imagesLibrary.get(imageHash) ?? (await generateAndRegister(imageHash));
if (!imageColor) return;
const { dataUri, ...rest } = imageColor;
return {
...rest,
imageHash
};
};
const generateAndRegister = async (imageHash: string) => {
const image = figma.getImageByHash(imageHash);
if (!image) return;
const bytes = await image.getBytesAsync();
const size = await image.getSizeAsync();
const { width, height } = await image.getSizeAsync();
const b64 = figma.base64Encode(bytes);
const mimeType = detectMimeType(b64);
const dataUri = `data:${mimeType};base64,${b64}`;
const mtype = detectMimeType(b64);
const dataUri = `data:${mtype};base64,${b64}`;
return {
width: size.width,
height: size.height,
mtype: mimeType,
const imageColor: ImageColor = {
width,
height,
mtype,
dataUri,
keepAspectRatio: true,
dataUri: dataUri,
id: '00000000-0000-0000-0000-000000000000'
};
imagesLibrary.register(imageHash, imageColor);
return imageColor;
};

View file

@ -1,6 +1,5 @@
import { Uuid } from './uuid';
//@TODO: check how this exports the image through a dataUri
export type ImageColor = {
name?: string;
width: number;
@ -9,4 +8,5 @@ export type ImageColor = {
id?: Uuid;
keepAspectRatio?: boolean;
dataUri?: string;
imageHash?: string; // @TODO: move to any other place
};

View file

@ -2,7 +2,7 @@ import { PenpotFile } from '@ui/lib/types/penpotFile';
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
import { Uuid } from '@ui/lib/types/utils/uuid';
import { parseFigmaId } from '@ui/parser';
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
import { createItems } from '.';
@ -15,7 +15,7 @@ export const createArtboard = (
file.addArtboard({
id,
shapeRef: shapeRef ?? parseFigmaId(file, figmaRelatedId, true),
fills: symbolFillGradients(fills),
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
...rest
});

View file

@ -1,7 +1,7 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { BoolShape } from '@ui/lib/types/shapes/boolShape';
import { parseFigmaId } from '@ui/parser';
import { symbolBlendMode, symbolBoolType, symbolFillGradients } from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolBoolType, symbolFills } from '@ui/parser/creators/symbols';
import { createItems } from '.';
@ -12,7 +12,7 @@ export const createBool = (
file.addBool({
id: parseFigmaId(file, figmaId),
shapeRef: parseFigmaId(file, figmaRelatedId, true),
fills: symbolFillGradients(fills),
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
boolType: symbolBoolType(boolType),
...rest

View file

@ -1,7 +1,7 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
import { parseFigmaId } from '@ui/parser';
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
export const createCircle = (
file: PenpotFile,
@ -10,7 +10,7 @@ export const createCircle = (
file.createCircle({
id: parseFigmaId(file, figmaId),
shapeRef: parseFigmaId(file, figmaRelatedId, true),
fills: symbolFillGradients(fills),
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
...rest
});

View file

@ -1,6 +1,7 @@
import { componentsLibrary } from '@plugin/ComponentLibrary';
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
import { uiComponents } from '@ui/parser/libraries';
import { createItems } from '.';
@ -12,10 +13,12 @@ export const createComponentLibrary = (file: PenpotFile) => {
return;
}
const { children = [], ...rest } = component;
const { children = [], fills, blendMode, ...rest } = component;
file.startComponent({
...rest,
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
id: uiComponent.componentId,
componentId: uiComponent.componentId,
mainInstancePage: uiComponent.mainInstancePage,

View file

@ -1,11 +1,7 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { PathShape } from '@ui/lib/types/shapes/pathShape';
import { parseFigmaId } from '@ui/parser';
import {
symbolBlendMode,
symbolFillGradients,
symbolPathContent
} from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolFills, symbolPathContent } from '@ui/parser/creators/symbols';
export const createPath = (
file: PenpotFile,
@ -14,7 +10,7 @@ export const createPath = (
file.createPath({
id: parseFigmaId(file, figmaId),
shapeRef: parseFigmaId(file, figmaRelatedId, true),
fills: symbolFillGradients(fills),
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
content: symbolPathContent(content),
...rest

View file

@ -1,7 +1,7 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { RectShape } from '@ui/lib/types/shapes/rectShape';
import { parseFigmaId } from '@ui/parser';
import { symbolBlendMode, symbolFillGradients } from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
export const createRectangle = (
file: PenpotFile,
@ -10,7 +10,7 @@ export const createRectangle = (
file.createRect({
id: parseFigmaId(file, figmaId),
shapeRef: parseFigmaId(file, figmaRelatedId, true),
fills: symbolFillGradients(fills),
fills: symbolFills(fills),
blendMode: symbolBlendMode(blendMode),
...rest
});

View file

@ -1,16 +1,33 @@
import { PenpotFile } from '@ui/lib/types/penpotFile';
import { TextShape } from '@ui/lib/types/shapes/textShape';
import { TextContent, TextShape } from '@ui/lib/types/shapes/textShape';
import { parseFigmaId } from '@ui/parser';
import { symbolBlendMode } from '@ui/parser/creators/symbols';
import { symbolBlendMode, symbolFills } from '@ui/parser/creators/symbols';
export const createText = (
file: PenpotFile,
{ type, blendMode, figmaId, figmaRelatedId, ...rest }: TextShape
{ type, blendMode, figmaId, content, figmaRelatedId, ...rest }: TextShape
) => {
file.createText({
id: parseFigmaId(file, figmaId),
shapeRef: parseFigmaId(file, figmaRelatedId, true),
content: parseContent(content),
blendMode: symbolBlendMode(blendMode),
...rest
});
};
const parseContent = (content: TextContent | undefined): TextContent | undefined => {
if (!content) return;
content.children?.forEach(paragraphSet => {
paragraphSet.children.forEach(paragraph => {
paragraph.children.forEach(textNode => {
textNode.fills = symbolFills(textNode.fills);
});
paragraph.fills = symbolFills(paragraph.fills);
});
});
return content;
};

View file

@ -1,4 +1,4 @@
export * from './symbolBlendMode';
export * from './symbolBoolType';
export * from './symbolFillGradients';
export * from './symbolFills';
export * from './symbolPathContent';

View file

@ -1,7 +1,10 @@
import { imagesLibrary } from '@plugin/ImageLibrary';
import { Fill } from '@ui/lib/types/utils/fill';
import { Gradient, LINEAR_TYPE, RADIAL_TYPE } from '@ui/lib/types/utils/gradient';
import { ImageColor } from '@ui/lib/types/utils/imageColor';
export const symbolFillGradients = (fills?: Fill[]): Fill[] | undefined => {
export const symbolFills = (fills?: Fill[]): Fill[] | undefined => {
if (!fills) return;
return fills.map(fill => {
@ -9,6 +12,10 @@ export const symbolFillGradients = (fills?: Fill[]): Fill[] | undefined => {
fill.fillColorGradient = symbolFillGradient(fill.fillColorGradient);
}
if (fill.fillImage) {
fill.fillImage = symbolFillImage(fill.fillImage);
}
return fill;
});
};
@ -29,3 +36,16 @@ const symbolFillGradient = ({ type, ...rest }: Gradient): Gradient | undefined =
console.error(`Unsupported gradient type: ${String(type)}`);
};
const symbolFillImage = ({ imageHash, ...rest }: ImageColor): ImageColor | undefined => {
if (!imageHash) return;
const imageColor = imagesLibrary.get(imageHash);
if (!imageColor) return;
return {
...rest,
dataUri: imageColor?.dataUri
};
};

View file

@ -1,4 +1,5 @@
import { componentsLibrary } from '@plugin/ComponentLibrary';
import { imagesLibrary } from '@plugin/ImageLibrary';
import { createFile } from '@ui/lib/penpot';
import { createComponentLibrary, createPage } from '@ui/parser/creators';
@ -7,8 +8,10 @@ import { PenpotDocument } from '@ui/types';
import { idLibrary } from '.';
export const parse = ({ name, children = [], components }: PenpotDocument) => {
export const parse = ({ name, children = [], components, images }: PenpotDocument) => {
componentsLibrary.init(components);
imagesLibrary.init(images);
uiComponents.init();
idLibrary.init();

View file

@ -1,8 +1,10 @@
import { PenpotPage } from '@ui/lib/types/penpotPage';
import { ComponentShape } from '@ui/lib/types/shapes/componentShape';
import { ImageColor } from '@ui/lib/types/utils/imageColor';
export type PenpotDocument = {
name: string;
children?: PenpotPage[];
components: Record<string, ComponentShape>;
images: Record<string, ImageColor>;
};