Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cairo blending modes #1247

Merged
merged 2 commits into from Nov 30, 2020
Merged

Cairo blending modes #1247

merged 2 commits into from Nov 30, 2020

Conversation

@tlubke
Copy link
Contributor

@tlubke tlubke commented Nov 24, 2020

motivation came from a conversation at the weekly discord meetup.

someone brought up that the cairo C API had support for different blending modes/operators as shown here: https://www.cairographics.org/operators/

I wrote a short norns script for testing all of the operators

local a = {}
a[0]  = "CLEAR"
a[1]  = "SOURCE"
a[2]  = "OVER"
a[3]  = "IN"
a[4]  = "OUT"
a[5]  = "ATOP"
a[6]  = "DEST"
a[7]  = "DEST_OVER"
a[8]  = "DEST_IN"
a[9]  = "DEST_ATOP"
a[10] = "XOR"
a[11] = "ADD"
a[12] = "SATURATE"
a[13] = "MULTIPLY"
a[14] = "SCREEN"
a[15] = "OVERLAY"
a[16] = "DARKEN"
a[17] = "LIGHTEN"
a[18] = "COLOR_DODGE"
a[19] = "COLOR_BURN"
a[20] = "HARD_LIGHT"
a[21] = "SOFT_LIGHT"
a[22] = "DIFFERENCE"
a[23] = "EXCLUSION"

local i = 0;

function init()
  redraw()
end

function enc(n, d)
  i = util.clamp(i+d, 0, 23)
  redraw()
end

function redraw()
  screen.clear()
  screen.level(15)
  screen.blend_mode(2) -- default
  screen.move(64, 8)
  screen.text_center(a[i])
  screen.level(8)
  screen.rect(32, 10, 64,32)
  screen.fill()
  screen.blend_mode(i)
  screen.level(4)
  screen.rect(48, 26, 64, 32)
  screen.fill()
  screen.update()
end

some of them are less friendly/useable than others, and I expect the list should be curated a bit.

the added C bits were kinda rushed, so I'm sure there's plenty to clean up.

a[22] = CAIRO_OPERATOR_DIFFERENCE;
a[23] = CAIRO_OPERATOR_EXCLUSION;

cairo_set_operator(cr, a[i]);

This comment has been minimized.

@ngwese

ngwese Nov 24, 2020
Member

suggest performing bounds checking of i to ensure one can’t generate a segv

@@ -449,5 +449,36 @@ extern void screen_export_png(const char *s) {
cairo_surface_write_to_png(surface, s);
}

void screen_set_operator(int i) {
CHECK_CR
cairo_operator_t a[24];

This comment has been minimized.

@ngwese

ngwese Nov 24, 2020
Member

the base type of the cairo_operator_t enum is an int, it may be more straightforward to cast i to the appropriate
type (after ensuring i contains a valid value)

This comment has been minimized.

@tlubke

tlubke Nov 24, 2020
Author Contributor

it may be more straightforward to cast i to the appropriate
type (after ensuring i contains a valid value)

Wouldn't this mean that all operators would have to be included then? There are some not in the array, such as CAIRO_OPERATOR_HSL_SATURATION which have no effect on grayscale values.

This comment has been minimized.

@csboling

csboling Nov 26, 2020
Contributor

Perhaps one option is to define a lookup table in Lua for the blend modes so that the "public" API lets you call them by name:

Screen.BLEND_MODES = {
  ['CLEAR'] = 0,
  ['SOURCE'] = 1,
  ...
}

Screen.blend_mode = function(mode)
  _norns.screen_set_operator(Screen.BLEND_MODES[mode])
end

Then the int passed to the _screen_set_operator weaver function could be cast to a cairo_operator_t and as a script author I would call screen.blend_mode('SOURCE') instead of remembering the numbering or defining my own lookup table.

This comment has been minimized.

@tehn

tehn Nov 27, 2020
Member

we have a similar issue regarding fonts, which is just a blind index (and the font names are (of minor assistance) reported at matron startup.

just to say, it'd be nice to set up a pattern to follow for similar cases

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 24, 2020

I took some screenshots of the test script.

destination/source priority

atop
dest_atop
dest_in
dest_over
dest
out
overlay
source

color/alpha blending

add
color_burn
color_dodge
darken
difference
exclusion
hard_light
lighten
multiply
overlay
saturate
screen
soft_light
xor

@ngwese
Copy link
Member

@ngwese ngwese commented Nov 24, 2020

it is great to see this. adding compositing modes has been on my personal todo/wish list for a long time.

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 26, 2020

More screenshots

add
color_burn
color_dodge
darken
difference
exclusion
hard_light
lighten
multiply
overlay
saturate
screen
soft_light
xor

@tehn
Copy link
Member

@tehn tehn commented Nov 27, 2020

apologies, minor collision--- please merge main and you should be good.

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 28, 2020

@tehn just getting to some of these fixes now.

we have a similar issue regarding fonts, which is just a blind index (and the font names are (of minor assistance) reported at matron startup.

just to say, it'd be nice to set up a pattern to follow for similar cases

So should I move setting up the array of cairo_operator_t to screen_init() similar to the font path setup and print each operator to stderr?

@tehn
Copy link
Member

@tehn tehn commented Nov 28, 2020

lol, apologies, i should've clarified.

here's my proposition, following @csboling

Screen.BLEND_MODES = {
  ['CLEAR'] = 0,
  ['SOURCE'] = 1,
  ...
}

Screen.blend_mode = function(mode)
  local m = mode
  if type(m) == "string" then m=Screen.BLEND_MODES[m] end
  _norns.screen_set_operator(m)
end

this way the index or the string works. you can list the modes with tab.print(screen.BLEND_MODES)

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 28, 2020

@tehn ah, got it!

Writing descriptions for each blending mode in the lua docs and I noticed that both Add and Saturate should result in alpha values of (aA + aB), but Saturate's clipping/masking behavior is different than Add's. It's also a bit hard to define in terms of behavior, as it seems to be acting like Dest.

What do you think of having the following as the documented modes, and leaving the cairo link for further information?

  • 0 Over (default)
  • 1 XOR: clears any overlapping pixels.
  • 2 Add: adds together the alpha (brightness) of overlapping pixels.
  • 3 Multiply
  • 4 Screen
  • 5 Overlay
  • 6 Darken
  • 7 Lighten
  • 8 Color_Dodge
  • 9 Color_Burn
  • 10 Hard_Light
  • 11 Soft_Light
  • 12 Difference
  • 13 Exclusion
@tlubke tlubke force-pushed the tlubke:cairo_blending_modes branch from 48857da to a7d5d32 Nov 29, 2020
Screen.blend_mode = function(index)
if type(index) == "string" then
local i = Screen.BLEND_MODES[string.upper(index)]
if i ~= nil then

This comment has been minimized.

@tlubke

tlubke Nov 29, 2020
Author Contributor

I wasn't able to test this with norns before pushing the commit, I suspect this would lead to the 'attempt to index nil value' error.

Actually I think this is fine, but I'm about to test it just in case.

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 29, 2020

Compiled on norns now and found a bunch of small errors, don't merge yet, there's another commit coming.

@tlubke
Copy link
Contributor Author

@tlubke tlubke commented Nov 29, 2020

should be good now. found an embarrassing amount of mistakes, can I squash the commits?

@catfact
Copy link
Collaborator

@catfact catfact commented Nov 29, 2020

sure, rebase and force-push on your fork/branch is fine.

* added static array of cairo_operator_t and set_operator() to screen.c

* added blend_mode() and BLEND_MODES lookup table to Screen.lua
@tlubke tlubke force-pushed the tlubke:cairo_blending_modes branch from cd3de58 to a46feb4 Nov 29, 2020
matron/src/hardware/screen.h Outdated Show resolved Hide resolved
* formatting fix in screen.h
@tehn
tehn approved these changes Nov 30, 2020
@tehn tehn merged commit 46e67df into monome:main Nov 30, 2020
@tehn
Copy link
Member

@tehn tehn commented Nov 30, 2020

thank you this new feature!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

5 participants