A feature request because I suck at colors: Color lines!

Hi! I suck at making color palettes. I have the right idea. My heart is in the right place, really. But sometimes I just can’t get the color juust right. My ramps are just slightly off and my tiny little art brain can’t hold everything in RAM at once. And I know there are patterns, I know there are ideals to make a color palette flow! Heck, I can almost visualize them using a trick I nabbed from a wonderful lesson on color palettes by Adam Younis. Essentially, dragging your cursor across your palette, you can start to visualize your color ramp on the hue-lightness table as a line.

frustration

But alas, my brain is juuust too smooth to be able to keep all of these in mind and tweak my colors to perfection!

This got me thinking, what if Aseprite were to have a tool to directly work with the lines generated by these color ramps?

The idea would be simple (for me to say, and surely hell for you to code, usually works out that way): as multiple colors are selected, draw a line from the first to the last showing the ramp created by the colors in that selection. The generated line would make it clear which of your colors aren’t ramping properly and help to understand patterns that lead to great palettes much more clearly and intuitively!

idea1

Now, keep in mind this is just some garbage mockup that I threw together in the last hour or two, but I believe it conveys the core idea pretty clearly! I’m sure there’s more that could make this concept more user-friendly, such as a partially transparent line, or the ability to visualize lines on other ranges than hue-lightness. I just didn’t feel like editing my mockup or making more. Listen, I made .gifs! Three of them! Those took long enough!

And because I love to make your dev lives hell and torture you with other ideas: The concept could potentially be combined with ideas such as this or this idea to add a color grouping option. This way, you could select multiple color groups and immediately visualize the range of colors you’ll have at your disposal with any given color palette!

idea2

Pretty neat, huh?

No?

Sort of? But it would take something like 12 years of dev time to implement?

Eh, fair. ¯\_(ツ)_/¯

1 Like

its a cool idea but if i were you just use the color sliders and use regular intervals in number bertween patterns, for example in the HSV sliders when im trying to make a shade i usually move 5 in the H to witchever direction is blue/purple, them 5-10 in S to the gray part and finally u move 5-10 in the V to the black part, to do tints i do the same but to yellow/orange, to the vibrant and to white if the saturaion on white reaches maximum you slowly desaturate
thats just one way to do it there are many, try experimenting in the hsv sliders

Well.

  1. It’s relatively easy to display multiple selected palette entries “dots” in Color selection widget — it has multiple modes, by the way — Color Tint/Shade/Tone, Color Spectrum, Color Wheels, even Normal Wheel. You can find them under top right button over Palette.

  2. It’s also quite easy to add mouse control over their dragging around Color selection widget, maybe with Ctrl+click or Shift+click to keep current way of color selection with just clicks.

  3. But it’s really questionable if we should draw lines between those “dots” with mutliple selections of palette entries. Well, you already can select multiple strides of colors with Shift+click & drag on Palette. So to draw those lines it will be required to fill array of arrays of {color_index1,color_index2} pairs and update them on any palette change so that they’ll try to keep those orders.

Colors other than foreground/background could be displayed as smaller “dots”.

So I think it is possible to add this feature, but maybe it should be hidden under checkboxes like “Show multiple colors” and “Show ramp lines”. And this is enhancement i.e. not that important.

That’s my suggestion.

I like the ideas you proposed! I’d honestly be perfectly happy settling for a feature that just showed multiple color ‘dots’ at once. It would probably be almost equally as useful, the more I think about it. The lines would be cool and all, but as you said, it definitely errs on the complicated side.

Honestly, it might be better without in many scenarios, in the context of more complicated ‘webs’ of color - I’m imagining scenarios two or three ramps that share a crossover color. How would you even make a line for that that you’re not just better off visualizing in your head yourself? I suppose there are ways it could technically be possible… You could create an instance of the color on the palette for each group, but then you have duplicate colors in your palette. You could make a system that allows for colors to belong to more than one group, but again, terribly overcomplicated for what is actually essential to the desired functionality.

At my idea’s core, I just want a way to visualize my entire palette on a color spectrum at once. The lines aren’t at all necessary for doing that!

Hi @rainedoe ,

Welcome to the forum! And thank you for sharing your idea proposal. You could try implementing your idea in Lua script, then increase functionality as you go along. So I tried it out. If you know a bit about scripting, or are willing to learn, this could be a starting point.

local dlg = Dialog {
    title = "Spectrum"
}

dlg:number{
    id = "width",
    label = "Width:",
    text = string.format("%.0f", 360),
    decimals = 0,
    visible = true
}

dlg:number{
    id = "height",
    label = "Height:",
    text = string.format("%.0f", 256),
    decimals = 0,
    visible = true
}

dlg:slider{
    id = "saturation",
    label = "Saturation:",
    min = 0,
    max = 100,
    value = 100
}

dlg:slider{
    id = "anchorSize",
    label = "Anchor:",
    min = 1,
    max = 64,
    value = 4
}

dlg:slider{
    id = "lineRes",
    label = "Dots Per Line:",
    min = 1,
    max = 64,
    value = 16
}

local function lerp(a, b, t)
    local u = 1.0 - t
    return Point(
        math.tointeger(u * a.x + t * b.x),
        math.tointeger(u * a.y + t * b.y))
end

local function drawDottedLine(a, b, res, func, strokeClr, brsh, cel, layer, frame)
    local len = res - 1
    local toStep = 1.0 / len
    for i = 0, len, 1 do
        local t = i * toStep
        local v = func(a, b, t)
        app.useTool {
            tool = "pencil",
            color = strokeClr,
            brush = brsh,
            points = {v},
            cel = cel,
            layer = layer,
            frame = frame
        }
    end
end

dlg:button{
    id = "ok",
    text = "OK",
    focus = false,
    onclick = function()
        local args = dlg.data
        if args.ok then
            local sprite = app.activeSprite
            if sprite then
                local frame = app.activeFrame or 1

                local w = args.width or 360
                local h = args.height or 255

                w = math.max(32, math.abs(w))
                h = math.max(32, math.abs(h))
                local xToNorm = 1.0 / w
                local yToNorm = 1.0 / h

                local spectrumLayer = sprite:newLayer()
                spectrumLayer.name = "Spectrum"
                local spectrumCel = sprite:newCel(spectrumLayer, frame)

                local spectrumImg = Image(w, h)
                local pxlitr = spectrumImg:pixels()
                local palette = sprite.palettes[1]

                local sat = args.saturation * 0.01

                local i = 0
                for elm in pxlitr do
                    local y = i // w
                    local x = i % w

                    local y01 = y * yToNorm
                    local x01 = x * xToNorm

                    local hue = x01 * 360.0
                    local light = 1.0 - y01

                    local clr = Color {
                        h = hue,
                        s = sat,
                        l = light,
                        a = 255
                    }

                    elm(clr.rgbaPixel)

                    i = i + 1
                end
                spectrumCel.image = spectrumImg

                local pointsLayer = sprite:newLayer()
                pointsLayer.name = "Color Alignment"
                local pointsCel = sprite:newCel(pointsLayer, frame)

                local clrPts = {}
                local trunc = math.tointeger
                local hueToWidth = 360.0 / w
                local len = #palette
                local lenn1 = len - 1
                for j = 0, lenn1, 1 do
                    local clr = palette:getColor(j)
                    local hslHue = clr.hslHue
                    local hslLight = clr.hslLightness
                    clrPts[1 + j] = Point(trunc(0.5 + hslHue * hueToWidth), trunc(0.5 + h * (1.0 - hslLight)))
                end

                local hueTol = 0.01
                table.sort(clrPts, function(a, b)
                    if math.abs(b.x - a.x) <= hueTol then
                        return a.y < b.y
                    end
                    return a.x < b.x
                end)

                -- Draw dotted lines.
                local brush = Brush(1)
                local strokeClr = Color(255, 255, 255, 255)
                local lineRes = args.lineRes

                for j = 1, lenn1, 1 do
                    local a = clrPts[j]
                    local b = clrPts[j + 1]

                    drawDottedLine(a, b, lineRes, lerp, strokeClr, brush, pointsCel, pointsLayer, frame)
                end

                local wrap0 = Point(0 - (w - clrPts[len].x), clrPts[len].y)
                drawDottedLine(wrap0, clrPts[1], lineRes, lerp, strokeClr, brush, pointsCel, pointsLayer, frame)

                local wrap1 = Point(w + clrPts[1].x, clrPts[1].y)
                drawDottedLine(clrPts[len], wrap1, lineRes, lerp, strokeClr, brush, pointsCel, pointsLayer, frame)

                -- Draw anchor points.
                local sz = args.anchorSize
                local offset = Point(sz, sz)
                for j = 1, lenn1 + 1, 1 do
                    local clrPt = clrPts[j]
                    local a = clrPt - offset
                    local b = clrPt + offset
                    app.useTool {
                        tool = "ellipse",
                        brush = brush,
                        color = strokeClr,
                        points = {a, b},
                        cel = pointsCel,
                        layer = pointsLayer,
                        frame = frame
                    }
                end

                app.refresh()
            else
                print("No active sprite.")
            end
        else
            print("Invalid arguments.")
        end
    end
}

dlg:button{
    id = "cancel",
    text = "CANCEL",
    onclick = function()
        dlg:close()
    end
}

dlg:show{
    wait = false
}

To be honest, I think the idea would be more complicated than it initially appears, due to color spaces (3D spaces at a minimum, not 2D, even after excluding alpha)

and due to what you suggested above - a bunch of 2D points could just as easily be connected into a graph with nodes and edges… but your palette remains a 1D array. That’s a much more difficult problem, though you could cluster near points together to divide it into a difficult problem with fewer elements. This might be interesting, in that case

Anyway, here’s a test I did

with this palette

JASC-PAL
0100
7
55 23 104
174 5 119
229 0 0
255 138 25
255 211 76
255 255 128
207 244 190

Then I tried playing around with gradient expansions of the ramp.

Here’s a plot of the CIE LAB ramp.

The intermediate points created by the gradients… wouldn’t necessarily lay on the straight line path between points of the original palette. That’s assuming that I implemented them correctly… you may want to use a more… professional gradient maker instead. And depening on results, a

may be preferable over a poly-line.

Best,
Jeremy

1 Like

jesus this went from 1/100 real quick i cant begin to fathom whats happening there

2 Likes

Maybe what I should say instead is this: I too suck at color, and I don’t fancy myself much of an artist either. But from experience, I can tell you that attempting to make good colors systematically through programming may look like the easier path at first. But it isn’t. You may have a much easier time practicing an artistic, intuitive approach instead.

1 Like

btw. here’s something for even more fun with colours: pure asbestos made this neat script which allows to run dawnbringer’s palette analyzer in aseprite (it doesn’t work perfectly, but it’s close):

lospec uses it as well, it’s pretty cool:

4 Likes

thats actually cool

@Olga_Galvanova ,

Thank you, that’s very neat and inspirational for the amount of diagnostic information it contains! The display is a bit overwhelming, though, I must admit. Which diagrams do you find the most helpful?

My 2c would be that a diagnostic tool would have to, after wading through the complexities, present the results in a simple enough way to the user that they feel their intuitions are affirmed and color choices are easier to make. This is harder in 2D than 3D, I think, though being able to cluster and categorize colors like the picture above is a big help. What I’m interested in is like the hexagons / isometric cubes in the image top right.

turnAround

The gif ate the color ramp, but it’s roughly the same as my earlier post (except that it’s a loop). Here’s a still of an alternative palette.

Because mixing in another colorspace outside of sRGB can lead to out-of-gamut colors, there’s the option to clamp the result once you’re bringing it back into sRGB or to normalize. That’s why there are 2 bars. The clamped one ocassionally has… clumpng. The normalized one looks too washed out to me.

I’m not suggesting CIE Lab is the only space–sounds like it’s got some issues that have lead people to seek out alternatives–but i’m inclined to use a uniform space as the default.

How would that be done with Aseprite in a performant way? Dunno.

Best,
Jeremy

1 Like

Wow, crazy detailed reply - I’ll be honest, I wouldn’t know the first place to start when it comes to actually doing any scripting with aseprite! I’d definitely be willing to give it a try though, at the very least to try out what you’ve provided! I’ll also have to check out some of the resources you linked; I actually am familiar with the coding adventure video, but the others seem like some fun rabbit holes to dive down!

1 Like

I enjoy a healthy mix of both! I’m no beginner to eyeballing and feeling out color palettes, and it definitely is key to getting good at it! but at the same time I’m always looking for interesting ways to visualize and make the process of doing it artistically easier! My ideal would be to tighten the feedback loop between tweaking colors and previewing/using colors to make it that much quicker and more intuitive to approach with an artistic brain, so that I’m not just flying by numbers using HSV sliders.

1 Like

@behreandtjeremy
i agree, there’s a lot happening and i have to admit i don’t understand everything there (for example what are these stripes on the left from indexed palette? sadly, there’s no documentation right now). what i strangely enough don’t find too useful are 3d representations of colour spaces. but that might be just me.
what i do like on db’s analyzer output are these features:

  • useful mixes table introduces dithers with colours close enough to not produce high contrast dither patterns
  • close colors graph shows pairs of colours visually indistinguishable, so those pairs might be considered to be consolidated into one colour
  • spectrum, polar and vector-scope-like graphs as they show distribution of colours across the spectrum (for instance in the example image i’ve posted we can clearly see the palette is very red-orange heavy with little green and even less blue and there are virtually no steps between hues. the value gradients are fairly separated and offer little possibility to shift from one hue to another. considering there are 50 colours in that palette, it is not a good thing.)
  • complementaries graph which shows gradients between complementaries across neutral area, which are, generally, very useful (more useful if there’s enough inbetween steps - which is not the case in the example palette) and demonstrate the idea of mixing hues through neutral colours.
  • primary ranges and brightness match range

speaking of tools - something db’s analyzer can’t cover by design are concepts like gamut masking, dominant colour or mother colour. i’ve always found it bit puzzling that any mixing tools i’ve ever seen are pretty barebones (you usually get some colour harmonies at max, which is not much). i guess there’s too many principles to cover and people might be lost pretty quickly.
but still, i’d love to have gamut masking feature. :]]

1 Like

Ditto gamut masking!

1 Like