Skip to content

Commit

Permalink
[nore-image] Implements padding
Browse files Browse the repository at this point in the history
Details:
- Adds a 1px margin around each image in the texture, to prevent some
  weird effects on the nodes surroundings
- Adds a relative padding (in percentage), implemented in the fragment
  shader
  • Loading branch information
jacomyal committed Feb 1, 2024
1 parent f07c555 commit 73cd5f9
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 20 deletions.
19 changes: 17 additions & 2 deletions packages/node-image/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface CreateNodeImageProgramOptions extends TextureManagerOptions {
// Allows overriding drawLabel and drawHover returned class static methods.
drawLabel: NodeLabelDrawingFunction | undefined;
drawHover: NodeLabelDrawingFunction | undefined;
// The padding should be expressed as a [0, 1] percentage.
// A padding of 0.05 will always be 5% of the diameter of a node.
padding: number;
}

const DEFAULT_CREATE_NODE_IMAGE_OPTIONS: CreateNodeImageProgramOptions = {
Expand All @@ -28,12 +31,14 @@ const DEFAULT_CREATE_NODE_IMAGE_OPTIONS: CreateNodeImageProgramOptions = {
keepWithinCircle: true,
drawLabel: undefined,
drawHover: undefined,
padding: 0,
};

const UNIFORMS = [
"u_sizeRatio",
"u_correctionRatio",
"u_cameraAngle",
"u_percentagePadding",
"u_matrix",
"u_colorizeImages",
"u_keepWithinCircle",
Expand All @@ -51,6 +56,7 @@ export default function getNodeImageProgram(options?: Partial<CreateNodeImagePro
drawLabel,
drawingMode,
keepWithinCircle,
padding,
...textureManagerOptions
}: CreateNodeImageProgramOptions = {
...DEFAULT_CREATE_NODE_IMAGE_OPTIONS,
Expand Down Expand Up @@ -167,13 +173,22 @@ export default function getNodeImageProgram(options?: Partial<CreateNodeImagePro
}

setUniforms(params: RenderParams, { gl, uniformLocations }: ProgramInfo): void {
const { u_sizeRatio, u_correctionRatio, u_matrix, u_atlas, u_colorizeImages, u_keepWithinCircle, u_cameraAngle } =
uniformLocations;
const {
u_sizeRatio,
u_correctionRatio,
u_matrix,
u_atlas,
u_colorizeImages,
u_keepWithinCircle,
u_cameraAngle,
u_percentagePadding,
} = uniformLocations;
this.latestRenderParams = params;

gl.uniform1f(u_correctionRatio, params.correctionRatio);
gl.uniform1f(u_sizeRatio, keepWithinCircle ? params.sizeRatio : params.sizeRatio / Math.SQRT2);
gl.uniform1f(u_cameraAngle, params.cameraAngle);
gl.uniform1f(u_percentagePadding, padding);
gl.uniformMatrix3fv(u_matrix, false, params.matrix);
gl.uniform1i(u_atlas, 0);
gl.uniform1i(u_colorizeImages, drawingMode === "color" ? 1 : 0);
Expand Down
13 changes: 10 additions & 3 deletions packages/node-image/src/shader-frag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ varying vec4 v_texture;
uniform sampler2D u_atlas;
uniform float u_cameraAngle;
uniform float u_percentagePadding;
uniform bool u_colorizeImages;
uniform bool u_keepWithinCircle;
Expand All @@ -20,7 +21,7 @@ const float radius = 0.5;
void main(void) {
float dist = length(v_diffVector);
float border = v_border;
vec4 color;
vec4 color = gl_FragColor;
float c = cos(-u_cameraAngle);
float s = sin(-u_cameraAngle);
Expand All @@ -41,10 +42,11 @@ void main(void) {
// Second case: Image loaded into the texture
else {
float paddingRatio = 1.0 + 2.0 * u_percentagePadding;
float coef = u_keepWithinCircle ? 1.0 : ${Math.SQRT2};
vec2 coordinateInTexture = diffVector * vec2(1.0, -1.0) / v_radius / 2.0 * coef + vec2(0.5, 0.5);
vec2 coordinateInTexture = diffVector * vec2(paddingRatio, -paddingRatio) / v_radius / 2.0 * coef + vec2(0.5, 0.5);
vec4 texel = texture2D(u_atlas, (v_texture.xy + coordinateInTexture * v_texture.zw), -1.0);
// Colorize all visible image pixels:
if (u_colorizeImages) {
color = mix(gl_FragColor, v_color, texel.a);
Expand All @@ -54,6 +56,11 @@ void main(void) {
else {
color = vec4(mix(v_color, texel, texel.a).rgb, max(texel.a, v_color.a));
}
// Erase pixels "in the padding":
if (abs(diffVector.x) > v_radius / paddingRatio || abs(diffVector.y) > v_radius / paddingRatio) {
color = u_colorizeImages ? gl_FragColor : v_color;
}
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions packages/node-image/src/stories/AllFeatures.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ const createPictogramsStage = () => {
{
type: "padding",
renderer: createNodeImageProgram({
padding: 5,
padding: 0.25,
}),
},
{
type: "padding-color",
renderer: createNodeImageProgram({
padding: 5,
padding: 0.25,
drawingMode: "color",
}),
},
Expand Down
24 changes: 11 additions & 13 deletions packages/node-image/src/texture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ export type TextureManagerOptions = {
// - If mode "force", will always scale images to the given value.
// - If mode "max", will downscale images larger than the given value.
size: { mode: "auto" } | { mode: "max"; value: number } | { mode: "force"; value: number };
// The padding should be expressed as a [0, 1] percentage.
// A padding of 0.05 will always be 5% of the diameter of a node.
padding: number;
// Tries to mimic the related CSS property.
objectFit: "contain" | "cover" | "fill";
// If true, the image is centered on its alpha barycenter.
Expand All @@ -35,13 +32,15 @@ export type TextureManagerOptions = {

export const DEFAULT_TEXTURE_MANAGER_OPTIONS: TextureManagerOptions = {
size: { mode: "max", value: 512 },
padding: 0,
objectFit: "cover",
correctCentering: false,
};

export const DEBOUNCE_TIMEOUT = 100;

// This margin helps avoiding images collisions in the texture:
export const MARGIN_IN_TEXTURE = 1;

/**
* Helpers:
* ********
Expand Down Expand Up @@ -87,8 +86,8 @@ export async function loadSVGImage(imageSource: string, { size }: { size?: numbe

const root = svg.documentElement;

let originalWidth = root.getAttribute("width");
let originalHeight = root.getAttribute("height");
const originalWidth = root.getAttribute("width");
const originalHeight = root.getAttribute("height");

if (!originalWidth || !originalHeight)
throw new Error("loadSVGImage: cannot use `size` if target SVG has no definite dimensions.");
Expand Down Expand Up @@ -207,11 +206,12 @@ export function drawTexture(ctx: CanvasRenderingContext2D, images: Record<string
const atlas: Atlas = {};
for (let i = 0, l = imagesArray.length; i < l; i++) {
const { key, image, sourceSize, sourceX, sourceY, destinationSize } = imagesArray[i];
if (x + destinationSize > predictedWidth) {
const destinationSizeWithMargin = destinationSize + MARGIN_IN_TEXTURE;
if (x + destinationSizeWithMargin > predictedWidth) {
maxRowWidth = Math.max(maxRowWidth, x);
x = 0;
y += currentRowHeight;
currentRowHeight = destinationSize;
currentRowHeight = destinationSizeWithMargin;
}
refinedImagesArray.push({
key,
Expand All @@ -226,12 +226,10 @@ export function drawTexture(ctx: CanvasRenderingContext2D, images: Record<string
atlas[key] = {
x,
y,
// This function handles rectangle for future-proof-ness, but the atlas
// actually expects squares.
size: Math.max(destinationSize, destinationSize),
size: destinationSize,
};
x += destinationSize;
currentRowHeight = Math.max(currentRowHeight, destinationSize);
x += destinationSizeWithMargin;
currentRowHeight = Math.max(currentRowHeight, destinationSizeWithMargin);
}

// 4. Crop texture to final best dimensions:
Expand Down

0 comments on commit 73cd5f9

Please sign in to comment.