Creating a Figma Plugin to Add Stroke to Bitmaps
The Plugin is Image Stroke.
The primary purpose of the Image Stroke plugin is to add vector strokes to bitmap layers in Figma. Similar to the Layer Style - Stroke
effect commonly found in software like Photoshop, this functionality has been missing in Figma.
Creating this plugin was driven by two main goals: one was to fulfill my own needs, and the other was to familiarize myself with the development workflow of Cursor.
As shown, the left image is a bitmap with a transparent background. Applying a stroke directly to it results in the middle image, where the stroke is applied to the layer rather than the image content. The right image demonstrates the desired effect achieved by this plugin.
https://figma plugin url
Why Use Image Stroke?
Traditional methods for adding strokes :
- Using other bitmap-to-vector plugins, adjusting parameters to generate a vectorized version of the entire content, and then manually adding a stroke.
- Exporting the image to Adobe Illustrator, expanding it to create a vectorized graphic, and then pasting it back into Figma.
- Exporting the image to Adobe Photoshop, adding a stroke directly, and exporting it again, which still results in a bitmap.
Image Stroke‘s advantages:
- Focuses solely on generating the outline stroke of the image content, without processing internal details, which may result in faster processing.
- Everything is done within Figma, eliminating the need to open other software like Adobe products.
- Automatically detects transparency thresholds (threshold), making it as plug-and-play as possible.
- Permanently free.
However, Image Stroke has its limitations:
- The contour detection algorithm still has room for improvement.
- Sometimes, the automatic detection of transparency thresholds (threshold) may fail, requiring manual parameter adjustments.
- Certain special images may not work correctly, necessitating additional manual adjustments to image properties.
Additional Notes:
Only supports single-image content strokes. If an image contains multiple disconnected elements, only the leftmost one will be stroked.FixedIt is recommended to place the image outside of a frame when using the plugin; otherwise, the generated stroke may be misaligned.Fixed- The plugin recognizes the original image, so it cannot generate paths for cropped images. The correct approach is to crop the image, export it as a PNG, and then use the plugin.
The following sections will provide detailed explanations of these points.
Parameter Explanation
Stroke Width and Stroke Color
- Simply input the values as you would for the Stroke property in Figma.
- The stroke corners are set to be smooth by default.
Sampling Rate
A higher value results in a simpler contour (1-10), with fewer anchor points and less detail.
- Note that the sampling rate is also related to the image size.
- For larger images, a higher sampling rate can be used.
- For smaller images, a smaller sampling rate is recommended.
- It is advisable to start with a value of 1 and adjust as needed.
- If the sampling rate is mismatched, no path will be generated.
- Note: Circular edges are considered complex edges.
Simplify Tolerance
A higher value results in a smoother path.
- The adjustment logic is similar to that of the sampling rate and is also related to edge complexity and image size.
- However, it is not recommended to increase both parameters simultaneously, as this may result in overly simplistic paths.
Edge Threshold
Similar to the threshold in vectorization algorithms, this parameter is related to the clarity of the image edges. As shown in the illustration, I manually reduced the edge clarity using a blur effect. The more blurred the edges, the lower the threshold needs to be to accurately follow the edges.
- For images with blurred or semi-transparent edges, adjusting the threshold is necessary to accurately capture the edges, especially after using tools like "remove background," which can complicate the transparency of the image edges. To achieve the desired effect, it is recommended to zoom in and experiment with different threshold values.
- Sometimes, the threshold may need to be set very low.
Closed Path Threshold
Closes the path when the distance between the starting and ending anchor points is less than this value; otherwise, the contour tracing continues. This will be discussed in the "Process and Principle" section.
This parameter is generally not modified, so hidden in the advanced settings.
Process and Principle
First, I must declare that I am not a programmer. Most of the plugin's code was generated by Cursor. My role was to propose requirements and guide Cursor in generating the code in the right direction.
Initially, I considered writing a vectorization function, but found it too complex and ineffective. I then researched the algorithm Photoshop uses to generate strokes and discovered the Distance Field series of algorithms. Valve had improved this algorithm for vectorizing textures and applied it in their Source engine (Improved Alpha-Tested Magnification for Vector Textures and Special Effects). However, this still wasn't what I was looking for—it needed to be simpler.
After further research, I found that the "Marching Ants" algorithm might be the answer. This seems to be an old yet common algorithm, though I was unaware of it due to my lack of expertise in this field. With some guidance from a friend, I gathered some materials and shared them with Cursor, which eventually led to the current code.
- Start scanning the image from the top-left corner, column by column, and from top to bottom within each column.
- Identify the first non-transparent pixel as the starting point.
- From the starting point, explore the transparency of the 8 surrounding pixels until the next contour point is found.
- A pixel is considered a contour point if it is not transparent and has at least one transparent pixel nearby.
- Trace other contour points in a specific direction and sampling rate to create a single, closed path.
- Use SVG's M command to generate anchor points and L to connect to the next anchor point. When the distance between the start and end points is less than a certain parameter, use Z to close the path.
After some black-box testing, I set the parameters for the automatic threshold, with the detected first pixel's transparency coefficient approximately set to 2. If the output is incorrect, increment the value by 1 until it reaches 255.
It's worth noting that image processing requires decoding the image into RGBA data, as mentioned in the Working with Images documentation. However, Cursor was not initially aware of this. As a user of AI-assisted coding, even if you're not writing the code yourself, it's important to understand the logical relationships in the code. Cursor could also evolve in this direction, such as offering one-click access to documentation from well-known libraries, in addition to notepads.
Limitations
Contour Detection Algorithm Still Has Room for Improvement
Currently, the scanning starts linearly from top to bottom and left to right. Scanning from all sides might be more efficient, though I'm not certain, and multiple tracing directions might affect path closure.
Automatic Edge Threshold Detection May Fail
As mentioned, manual parameter adjustments may be necessary. To manually set the threshold, zoom in on the image until you can see the square pixels. Locate the first pixel in the top-left corner and estimate its transparency threshold. The lower the pixel's transparency, the lower the threshold parameter should be.
Some Special Images May Not Work
Currently, only PNG images are supported. You may need to manually export the image or use ⇧⌘C. Due to the limited number of test images, there may be cases where adjusting parameters doesn't work. I will consider further optimizations if time permits.
Only Supports Single-Image Content Strokes
If an image contains multiple disconnected elements, only the leftmost one will be stroked.
Plugin Recognizes the Original Image
The plugin cannot generate paths for cropped images. The correct approach is to crop the image, export it as a PNG, and then use the plugin.
No Open-Source Repository
Since most of the code was generated by AI and is not particularly complex, I feel that uploading it to GitHub might not be appropriate. If there's significant interest in the source code, I may consider opening a repository.