-
Notifications
You must be signed in to change notification settings - Fork 61
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
Add {into,from}_u32
methods for RGB/A, Packed struct for u32 representations of RGBA
#170
Conversation
There are a few different ways to do this and I wasn't sure what was best. I've seen all different orderings in libraries I've used, so I figured it's best to include them all. I wasn't sure if I should implement any further derives on the structs like serialization or I ended up going back and figuring out the trait bounds for hex methods inside of Rgb/Rgba itself. It didn't save much in the packed structs but they're nice convenience methods to have that save typing all the commas or periods when prototyping. |
This is an interesting way of tackling it. I would probably just have made a simple conversion method like Thinking a bit about this, I think making to/from_hex be parametric on the value type would be to over engineer it for no good reason if there are separate packed types that can have their own converters. The bare Just to exchange ideas, would something like this make sense? See it as a sketch, but the idea is to twist the concept a bit to turn the channel ordering into a type parameter (allowing operations on any packed RGB) and unify that with the /// Splits and combines RGB(A) types with some channel ordering.
pub trait RgbChannels {
fn split_rgb<S>(rgb: Rgba<S, u8>) -> (u8, u8, u8, u8);
fn combine_rgb<S>(channels: (u8, u8, u8, u8)) -> Rgba<S, u8>;
}
struct Abgr;
impl SplitChannels for Abgr {
//...
}
//...
// Adding Pixel here makes it easy to convert to and from slices of u32.
#[derive(..., Pixel)]
#[repr(transparent)]
struct PackedRgb<C: RgbChannels> {
color: u32,
channel_order: PhantomData<C>, // This is not a nice aspect of this, but implementing `From<u32>` should make it less painful to construct.
}
impl<S, C: RgbChannels> Into<Rgb<S, u8>> for PackedRgb<C> {
fn into(self) -> Rgb<S, u8> {
//...
C::combine_rgb((c1, c2, c3, c4)).color
}
}
// rgb.rs:
impl<S> Rgb<S, u8> {
fn from_hex<C: RgbChannels>(color: u32) -> Self {
PackedRgb::<C>::from(color).into()
}
fn into_hex<C: RgbChannels>(self) -> Packed<C> {
PackedRgb::from(self).color
}
} Whichever way it's done, I think it's best to start with a conservative approach and only allow it for RGB(A)8, to begin with. That's the most straightforward one and anyone who want to convert to any other component format can easily do it anyway. |
I liked those ideas a lot and thought they would clean up some parts nicely. 👍 It was very helpful and chopped down about 100 lines of repetitive code. Luckily I had the test data all there waiting for the implementation to finish which gave me confidence in refactoring, and the tests barely needed changing. During my first go, I did try some type of channel ordering marker data but that and other complexity got in the way of a rough initial implementation. The bounds trip me up with the error messages not being that helpful sometimes and any time I write what I think should work I run into features that are currently unstable, but I think I'm starting to understand a bit more. In no particular order, there are a few implementation details which came up in the refactor that I'm not sure about.
|
{into,from}_hex
methods for RGB/A, PackedRgb struct for u32 representations of RGBA
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a concept, it seems to work out. I think it's possible to iron out a couple of those remaining wrinkles with good shortcuts.
I addressed some of your point as inline comments too, but overall, I'm open for alternate names (and module structures) to everything, wherever it helps. We are essentially implementing the same concepts (RGB encodings) in two different formats, so name collisions are unfortunately inevitable to some extent.
Looking around online, it seems like it's mostly just RGBA when the alpha channel is mentioned, and [?]RGB when it's not there (an emergent side effect of the bit shifting, I suppose). This makes it hard to really give a universal default, so ARGB is as good as any if a default is necessary. This is more of a "soft" property of the library that's hard to say if it's an improvement or not without studying how it's used. It may even be that it's possible for the language to infer it from containers or return types where it ends up being used.
As for the default for alpha, I wonder if it would be any bad consequences if we pick 0xFF
. I'm thinking it may even have the opposite effect of being helpful when it's interpreted on the receiving end.
I'm not sure why it doesn't infer u8
components as the only alternative, but type inference and defaults can be weird sometimes. I believe the issue may be minimized if we implement and maybe somehow promote From<u32>
as a shortcut. Then it would just be Srgb::<u8>::from(...)
at worst.
{into,from}_hex
methods for RGB/A, PackedRgb struct for u32 representations of RGBA{into,from}_u32
methods for RGB/A, Packed struct for u32 representations of RGBA
I changed to 0xFF again because that's probably more in line and expected during use to have full alpha for an RGB color. If you want 0x00 for the alpha use an
What would this look like roughly? If I try to implement for |
I was able to simplify the This is the current implementation. I tried to piggyback on Rgba when I could it didn't seem possible to use anything more. fn into(self) -> Rgb<S, u8> {
let bytes: [u8; 4] = self.color.to_be_bytes();
let color: Alpha<Rgb<S, u8>, u8> = C::combine_rgb((bytes[0], bytes[1], bytes[2], bytes[3]));
Rgb::from_components((color.red, color.green, color.blue))
} Is there an easier way to convert from error[E0277]: the trait bound `u8: FromF64` is not satisfied
--> palette/src/rgb/packed.rs:158:19
|
158 | Rgb::from(color)
| ^^^^^ the trait `FromF64` is not implemented for `u8`
|
= note: required because of the requirements on the impl of `component::FloatComponent` for `u8`
= note: required because of the requirements on the impl of `std::convert::From<alpha::Alpha<rgb::rgb::Rgb<S, u8>, u8>>` for `rgb::rgb::Rgb<_, u8>`
= note: required by `std::convert::From::from` |
We are probably in name confusion land. I had this in mind: impl<S: RgbStandard> From<u32> for Rgb<S, u8> {
fn from(color: u32) -> Self {
Self::from_u32::<Argb>(color)
}
}
impl<S: RgbStandard> From<u32> for Alpha<Rgb<S, u8>, u8> {
fn from(color: u32) -> Self {
// Notice the ordering here, going for the seemingly popular one.
Self::from_u32::<Rgba>(color)
}
} That way one can get quick and dirty default conversions. I suppose |
Oh I see what you mean now. That works nicely and gives us a somewhat sane "universal" behavior. I assume that should live in I changed the other |
I would probably have placed it in rgb.rs, since it doesn't mention anything from packed.rs publicly, but both are valid option! (No need to change) Yes, the Anyway, I do really like the way it's implemented now! Very nice! Thanks! The only improvements I can think of now is just a tiny bit of documentation polish:
Essentially a couple of "did you know..." examples, that I think may make someone's life a bit easier. Other than those details, I'm ready to merge when you are! |
Nice! I think that's all for this one! Feel free to squash it. |
Those examples were good ideas, and I'm happy with how this turned out. 👍 Massive Github outage so wasn't able to post comments or push. I'll squash this now. I've not used the I was trying to think of ways to improve discovery or surface these features. I noticed there's no |
I think the fact that it refers to the Pixel trait, combined with the small and straight forward example, shows what it may be good for. You are right in that it's not super easy to find examples from the documentation root. Part of why I haven't put the The examples and documentation is in need of a bit of an overhaul, I think, but that's much larger than this PR. Hence why I'm not requiring anything more fancy than this. I would like to document various common patterns too, a bit like making a cookbook, for people to look up how to do things like read and write to images and/or pixel buffers, how to interoperate with other graphics crates, how to set up a basic linear pipeline, and all sorts of other things one may use this for. |
But the point of the Pixel trait is to be glue code for converting arrays and slices to Palette types. If you have some data that can be represented as a slice or array, you can turn it into a Palette color without copying anything. It's pretty neat to be able to take a massive |
Add `Packed` struct which stores Rgb/a colors as a packed u32 with support for multiple channel orders: ABGR, ARGB, BGRA, and RGBA. Add `RgbChannels` trait for splitting and combining of Rgba components in the previously mentioned channel orderings. Add corresponding `From` impls for `Packed`, `Rgb/a`, `u32`
That makes sense, I can imagine some applications with a slice of u32. I think that wraps this up. I accidentally re-inserted a gap between the documentation and the |
Alright! Let's get it merged. bors r+ |
Build succeeded |
Add
into_u32
andfrom_u32
methods forRgb
andRgba
.Add
Packed
struct which stores Rgb/a colors as a packed u32 with support for multiple channel orders: ABGR, ARGB, BGRA, and RGBA.Add
RgbChannels
trait for splitting and combining of Rgba components in the previously mentioned channel orderings.Add corresponding
From
impls forPacked
,Rgb/a
,u32
Closes #144