From acf2f4280c8e0194f9a64f36000abb7cd944e415 Mon Sep 17 00:00:00 2001
From: Kelly Yang <124ykl@gmail.com>
Date: Sun, 8 Mar 2026 09:11:19 -0700
Subject: [PATCH] fix(maskeditor): make brush size slider logarithmic (#8097)
(#9534)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
fix #8097.
This PR shifts the Mask Editor Brush Size slider from a linear scale to
a logarithmic (exponential) scale. Previously, the linear 1-250 range
heavily clumped the usable, small "fine-detail" brush sizes (e.g., 1px
to 20px) into the very first 10% of the slider, making it extremely
difficult to select precise sizes with the mouse.
This update borrows UX paradigms from other standard image editors like
Photoshop and GIMP, which map their scale entry widgets on an
exponential curve.
## GIMP Source
By inspecting the official **GIMP** source code under
`libgimpwidgets/gimpscaleentry.c`, we can see this exact mathematical
relationship being utilized when the logarithmic property is marked TRUE
on a brush radius adjustment widget:
```
// Mapping visual slider to internal value
value = gtk_adjustment_get_lower(...) + exp(t);
// Mapping internal value to visual slider
t = log (value - gtk_adjustment_get_lower(...) + 0.1);
```
https://github.com/user-attachments/assets/6d59ff12-f623-42cc-a52b-84147e9bb90b
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9534-fix-maskeditor-make-brush-size-slider-logarithmic-8097-31c6d73d365081118508e8363e0c5312)
by [Unito](https://www.unito.io)
---
.../maskeditor/BrushSettingsPanel.vue | 28 ++++++++++++++++---
1 file changed, 24 insertions(+), 4 deletions(-)
diff --git a/src/components/maskeditor/BrushSettingsPanel.vue b/src/components/maskeditor/BrushSettingsPanel.vue
index 4778d2cff3..37a78b11bd 100644
--- a/src/components/maskeditor/BrushSettingsPanel.vue
+++ b/src/components/maskeditor/BrushSettingsPanel.vue
@@ -72,12 +72,12 @@
/>
@@ -182,6 +182,26 @@ const brushSize = computed({
set: (value: number) => store.setBrushSize(value)
})
+const rawSliderValue = ref(null)
+
+const brushSizeSliderValue = computed({
+ get: () => {
+ if (rawSliderValue.value !== null) {
+ const cachedSize = Math.round(Math.pow(250, rawSliderValue.value))
+ if (cachedSize === brushSize.value) {
+ return rawSliderValue.value
+ }
+ }
+
+ return Math.log(brushSize.value) / Math.log(250)
+ },
+ set: (value: number) => {
+ rawSliderValue.value = value
+ const size = Math.round(Math.pow(250, value))
+ store.setBrushSize(size)
+ }
+})
+
const brushOpacity = computed({
get: () => store.brushSettings.opacity,
set: (value: number) => store.setBrushOpacity(value)