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

Adding color handling #90

Merged
merged 13 commits into from
Feb 16, 2024
Merged

Adding color handling #90

merged 13 commits into from
Feb 16, 2024

Conversation

FayCarsons
Copy link
Collaborator

My first attempt at a system for coloring shapes and setting background in init. Shapes now have a color field that is an 8bit RGB three-tuple. Shape constructors are the same, inserting black as default, but shapes can be piped to a new function with_color that returns a new shape with the color field set to the arg.

So creating a gray circle can look like this:

let c = circle 50 |> with_color (128, 128, 128)

Copy link
Owner

@Sudha247 Sudha247 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is useful refactoring. Some comments below.

examples/circle_packing.ml Outdated Show resolved Hide resolved
lib/color.ml Show resolved Hide resolved
lib/shape.mli Outdated
Comment on lines 2 to 5
type circle = { c : point; radius : float; color : Color.color }
type ellipse = { c : point; rx : float; ry : float; color : Color.color }
type polygon = { vertices : point list; color : Color.color }
type line = { a : point; b : point; color : Color.color }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we need not expose the internals of shape types in the mli file. But, I see that they're used in the Transform module, so we can't do away with it. Makes me wonder whether we should put them all in the Shape module.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put all the transforms in shape.ml? I don't think that would bulk it up too much so I wouldn't have a problem with it, but I think I do have a small preference for breaking up projects like this.

@nikochiko
Copy link
Collaborator

Thanks! This looks great!

I have suggested some renaming and refactor changes. Please go through the review.

@@ -7,7 +8,7 @@ let max_radius = 150.
let num_circles = 5_000
let max_attempts = 100_000
let shrink_factor = 0.85
let _ = Stdlib.Random.self_init ()
let _ = Rand.self_init ()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This renaming seems unnecessary. Because Random is part of Stdlib, you can e.g. do Random.self_init. I would recommend that so it is easier to read for someone who knows the module (no need to wonder if this is part of the stdlib or a module defined in this repo).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning on entirely rewriting this example as it broke when we migrated to the Cairo backend. Will keep comments regarding it in mind, though

@@ -29,20 +30,21 @@ let palette =

(* utility Functions *)

let rand_nth coll = List.length coll |> Rand.full_int |> List.nth coll
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is coll here?

Can you think of a name so it is obvious what it stands for?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also refactor this to make the random selection clearer. Something like this:

let n = Random.full_int (List.length coll) in
List.nth n coll

I like this blog's explanation on why it's helpful (find for Explaining variables) https://henrikwarne.com/2024/01/10/tidy-first/


(* creates a circle with a random center point and radius *)
let rand_circle () =
let point = rand_point () in
(point, min_radius +. Stdlib.Random.float (max_radius -. min_radius))
(point, min_radius +. Rand.float (max_radius -. min_radius))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as above, please use Random directly

List.map
(fun ((x, y), radius) ->
circle
~c:(point (int_of_float x) (int_of_float y))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay to lose the accuracy here when converting from float to int?

lib/context.ml Outdated
@@ -65,3 +52,15 @@ let save () =

let restore () =
match !context with Some ctx -> Cairo.restore ctx.ctx | None -> fail ()

let init_context background_color line_width ((x, y) : int * int) axes =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't only have a type signature for one parameter.
That should be in the .mli file.

Also, if these are the dimensions of the canvas, w and h would be more consistent names - x, y for point coordinates, and w, h for canvas size

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah this was left over from the type inference system insisting that these were floats, I should just be able to remove it.

Will rename x,y as well

@@ -10,7 +10,7 @@ val fail : unit -> unit

exception Context of string

val init_context : float -> int * int -> bool -> unit
val init_context : int * int * int * int -> float -> int * int -> bool -> unit
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a named type for int * int * int * int, but that doesn't have to be part of this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an RGBA color which is only used in setting the background color. Which is why I was hesitant to give it an alias. I think our current color type should probably keep its name, any thoughts what this should be named?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflict is why I thought it would be better to deal with it later. This is OK for now and shouldn't block the merge.

Q - can we have only one color type for both background/fill that has RGBA parts, or is that not supported by Cairo for strokes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought was that since the focus of this library is simplicity, making users deal with an alpha value(which is almost always going to be at 255/1.0) every time they want to set the color of a shape would be a nuisance.

In my experience, creative coding libraries have a 'set_color' function that takes RGB and maybe another that takes RGBA or just alpha.

Cairo (and I believe HTML canvas) does support both

@FayCarsons
Copy link
Collaborator Author

Applied changes, caught a couple of small issues, and added color mapping functions: map_color which applies a fn to both stroke and fill, and map_stroke/map_fill which apply a fn to specific color fields in a shape.


(** Converts RGB color into opaque RGBA color.
For use w/ `Context.background` *)
let opaque (r, g, b) = (r, g, b, 255)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the same function to create custom colours as well?

Copy link
Collaborator Author

@FayCarsons FayCarsons Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you're asking, this function is for setting the background color in the init function. To make it easier to use RGB colors, that are maybe being used elsewhere, as an opaque background color. It wouldn't work for adding stroke or fill fields because those don't have an alpha field.

Do you think there should be a color constructor function? I didn't add one because I thought a three-tuple was easy enough to construct.

Copy link
Owner

@Sudha247 Sudha247 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left the minor comment above, otherwise LGTM! 👍

@FayCarsons
Copy link
Collaborator Author

Fixed merge conflicts!

@nikochiko nikochiko merged commit 7a0f23d into Sudha247:main Feb 16, 2024
2 checks passed
@FayCarsons FayCarsons deleted the color-system branch March 22, 2024 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants