-
Notifications
You must be signed in to change notification settings - Fork 41
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 group layers support #13
Conversation
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.
AWESOME work as usual!
Left some feedback. Note that I only spent 20-30 mins thinking about this so there will be places where I'm completely wrong or missing the mark. Feel free to call those out.
Thanks! Nice work!!
src/lib.rs
Outdated
@@ -117,24 +117,19 @@ impl Psd { | |||
impl Psd { | |||
/// Get all of the layers in the PSD | |||
pub fn layers(&self) -> &Vec<PsdLayer> { | |||
&self.layer_and_mask_information_section.layers | |||
self.layer_and_mask_information_section.group.layers() |
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.
What's the idea behind this change?
} | ||
|
||
impl PsdLayerGroup { | ||
/// Get all of the layers in the PSD |
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.
Isn't this all of the layers in the group, not necessarily the entire PSD?
pub struct PsdLayerGroup { | ||
pub(in crate) layers: Vec<PsdLayer>, | ||
/// A map of layer name to index within our layers vector | ||
pub(in crate) layer_names: HashMap<String, usize>, |
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.
layer_indices
or layer_name_indices
(I know the name was already there and you just moved it)
} | ||
|
||
/// Get a layer by name | ||
pub fn layer_by_name(&self, name: &str) -> Result<&PsdLayer, Error> { |
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.
This should return Option<&PsdLayer>
Then the code
let layer_idx = self.layer_indices.get(name)?;
self.layers.get(layer_idx)
/// Get a layer by index. | ||
/// | ||
/// index 0 is the bottom layer, index 1 is the layer above that, etc | ||
pub fn layer_by_idx(&self, idx: usize) -> Result<&PsdLayer, Error> { |
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.
Same here should return an option
// Get one of the PsdLayerChannels of this PsdLayer | ||
fn get_channel(&self, channel: PsdChannelKind) -> Option<&ChannelBytes> { | ||
self.channels.get(&channel) | ||
} | ||
} | ||
|
||
/// Section divider constants. |
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.
No need for these constants. The values can be inlined in the match statement since they're directly next to the enum variants that they map to
DIVIDER_OPEN_FOLDER => GroupDivider::OpenFolder, | ||
DIVIDER_CLOSED_FOLDER => GroupDivider::CloseFolder, | ||
DIVIDER_BOUNDING_SECTION => GroupDivider::BoundingSection, | ||
_ => GroupDivider::NotDivider |
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.
Similarly here - we have a type GroupDivider
that has the ability to not be a divider. More idiomatic to just have an option (a noted below)
@@ -137,6 +237,8 @@ pub struct LayerRecord { | |||
pub(super) bottom: i32, | |||
/// The position of the right of the image | |||
pub(super) right: i32, | |||
/// Group divider tag, `GroupDivider::NotDivider` for not divider layers | |||
pub(super) divider_type: GroupDivider, |
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.
Option<GroupDivider>
pub(in crate) layers: Vec<PsdLayer>, | ||
/// A map of layer name to index within our layers vector | ||
pub(in crate) layer_names: HashMap<String, usize>, | ||
pub(crate) group: PsdLayerGroup, |
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.
I'm finding this to be confusing
Wouldn't the layer and mask information section have names and groups?
Maybe something like:
pub struct LayerAndMaskInformationSection {
layers: Vec<PsdLayer>,
layer_indices: HashMap<String, usize>,
groups: HashMap<String, Range<usize>>
}
use failure::{Error, Fail}; | ||
use std::collections::HashMap; | ||
|
||
/// PsdLayerGroup represents a group of layers |
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.
I'm curious about your thoughts on this data structure.
I feel like we should treat the layers as a just bottom to top list of layers .. then have groups just be pointers to some first and last layer. Which is effectively what it is in the spec - just a pointer to the top of one layer and the bottom of another.
Feels more lightweight to be vs. needing to have layers and sub-layers and sub-sub-layers etc.
So, I've got a question. |
Hmm.. Maybe we should be returning I'm curious what you suggest here? |
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.
A M A Z I N G work - I don't know how you're so fast haha.
Left lots of minor notes. Structurally this looks awesome to me - just some small bits of feedback scattered around.
Love the direction that you went and I learned a couple things!
} | ||
|
||
impl<T> KeyIDContainer<T> { | ||
/// Creates a new `KeyIDContainer` |
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.
A lot of these doc comments don't give any new information. Would replace the ones that are just repeating the method name with an #[allow(missing_docs)]
annotation
|
||
/// Get all of the groups in the PSD | ||
pub fn groups(&self) -> &Vec<PsdGroup> { | ||
&self.layer_and_mask_information_section.groups.items() |
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.
Hmmm items
seems like an unexpected word - but I'll keep reading the PR to figure out what this means
use std::ops::{Index, Range}; | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct KeyIDContainer<T> { |
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.
Would doc comment this with the purpose / use case of the abstraction
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.
Also - idea for a name - NamedItems<T>
?
} | ||
} | ||
|
||
impl<T> Index<usize> for KeyIDContainer<T> { |
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.
Sweet
} | ||
} | ||
|
||
impl<T> Index<&str> for KeyIDContainer<T> { |
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.
Sweet
|
||
let mut parse_group_now = false; | ||
let mut range_stack = vec![]; | ||
let mut nested_level = 0; |
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.
Do we need nested_level
? Isn't it just always range_stack.len()
?
}; | ||
|
||
let group_id = group_id.unwrap(); | ||
let parent_group_id = if group_id > 1 { |
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.
Why is this >1
instead of >0
? I'm a little confused
|
||
// Check layer outside group | ||
psd.layer_by_name("Second Layer").unwrap(); | ||
// Check layer inside group | ||
let group = psd.layer_by_name("group").unwrap(); | ||
group.layers().unwrap().layer_by_name("First Layer").unwrap(); | ||
psd.group_by_name("group").unwrap(); |
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.
Since there are two layers can our test make sure that this group contains the correct layer?
|
||
// Check first group | ||
let group = psd.layer_by_name("group").unwrap(); | ||
group.layers().unwrap().layer_by_name("First Layer").unwrap(); | ||
psd.group_by_name("group").unwrap(); |
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.
Same here - can we verify that each group contains the correct layers
assert_eq!(psd.groups().len(), 2); | ||
|
||
// Check group | ||
let group = psd.group_by_name("group outside").unwrap(); |
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.
Same here - can we verify that one group points to no layers and the other group points to one layer
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.
Also can we verify that the child group has its parent group set properly
Something like (just a rough sketch):
let parent = psd.group_by_name("group_inside").unwrap().parent_group().unwrap();
assert_eq!(psd.group_by_id(parent).unwrap().name, "group_outside")`
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.
Otherwise these tests aren't verifying that we're pulling the correct group data - just that we're pulling groups at all
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.
Nice stuff! I think the group parenting logic might be slightly off so I left a note about that
// Read each layer's channel image data | ||
for (idx, layer_record) in layer_records.into_iter().enumerate() { | ||
let group_id = if nested_level > 0 { | ||
let group_id = if group_start_stack.len() > 0 { | ||
Some((groups_count - groups.len() - 1) as u32) |
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.
I'm confused by this line. It reads as "Number of groups minus number of groups minus 1". What is this doing?
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.
- Layers are present in reverse order. Then, the first layer record contains last layer, so
group id
will betotally_number_of_groups - already_seen_groups
. - Also, ID starts from 0, because it's a index, then -1.
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.
Oh I think I'm following.
So this is leading to GroupDivider::Close
opening the folder and GroupDivider::Open
closing the folder.
If that's the same - could we just for (idx, layer_record) in layer_records.into_iter().enumerate().reverse()
instead of having this inversed logic?
end: idx, | ||
}; | ||
|
||
let group_id = group_id.unwrap(); | ||
let parent_group_id = if nested_level > 0 { | ||
let parent_group_id = if group_start_stack.len() > 0 { | ||
Some(group_id - 1) |
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.
Could we add a test PSD with the following structure:
Group
Group
Layer
Group
Layer
And verify that each of the two sub-groups Group has the correct parent_group_id?
I ask this because right now I think that the logic wouldn't work for this case. It seems like the third parent group will have a parent_group_id of 1 when it should be 0
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.
My only real feedback/questions are around the grouping logic - so I asked a few questions about what you're going for.
Overall the group parsing logic feels a bit more complex than it needs to be - but maybe I'm missing something?
I would've imagined something closer to what you had before with a simple stack that we push to and pop from in order to track the parent group. What changed to lead to removing that approach?
Thanks - nice work!
let frame = Frame { | ||
start_idx: layers.len(), | ||
name: layer_record.name, | ||
group_id: already_viewed, |
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.
What is already viewed representing?
@@ -59,6 +59,14 @@ pub struct LayerAndMaskInformationSection { | |||
pub(crate) groups: NamedItems<PsdGroup>, | |||
} | |||
|
|||
#[derive(Debug)] |
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.
What is a frame?
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.
Frame represents a stack frame. It stores all needed data in one struct.
let mut layers = NamedItems::with_capacity(layer_count); | ||
let mut groups: NamedItems<PsdGroup> = NamedItems::with_capacity(group_count); | ||
|
||
let mut levels: Vec<Vec<Frame>> = vec![]; |
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.
What are levels?
// Read each layer's channel image data | ||
for (layer_record, channels) in layer_records.into_iter() { | ||
// get current group from stack | ||
let current_group_id = stack.last().unwrap().group_id; | ||
|
||
match layer_record.divider_type { | ||
// open the folder | ||
Some(GroupDivider::CloseFolder) | Some(GroupDivider::OpenFolder) => { |
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.
Why is this both close and open folder?
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.
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.
Ohhhhhhh - nice!
Mind just commenting that on their enum definitions?
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.
Feel free to merge when you're ready!
Published as v0.1.7 |
Fixes #10