Introduction

psd provides a Rust API for parsing and working with the Adobe Photoshop File Format.

psd seeks to make it easy for you to write scripts that work with Photoshop files.


The Photoshop specification is large so, while we support the main parts of it, not every little bit is supported.

If there's something that you'd like to get your hands on please feel free to open an issue.

User Guide

This chapter covers the basics on how to work with some of the more common aspects of PSD files.

Use this chapter to get a higher level introduction to the features of the psd crate.

After that you can explore the full API documentation to see all of the types and functions that are available.

Only Exporting Certain Layers

One use case that the psd crate is designed to support is an asset pipeline where you're extracting image data from PSD files in order to convert it into the format that your application works with (such as PNG).

In order to support that use case we grant control over exactly which layers in your PSD get exported via the flatten_layers_rgba funcion.

You provide a filter function and any layers that pass your filter (return true) will get blended from top to bottom into a final image.

Here's an example of compositing a final image from layers that do not have names that begin with an underscore.

let psd = include_bytes!("./my-psd-file.psd");

let pixels: Vec<u8> = psd.flatten_layers_rgba(&|(_idx, layer) {
   !layer.name().started_with("_")
}).unwrap();

Layer Groups

psd has support for parsing and getting information about layer groups.

Here are some examples:


# #![allow(unused_variables)]
#fn main() {
use psd::Psd;
const TOP_LEVEL_ID: u32 = 1;

/// group structure
/// +---------------+----------+---------+
/// | name          | group_id | parent  |
/// +---------------+----------+---------+
/// | group inside  | 2        | Some(1) | refers to 'group outside'
/// | group outside | 1        | None    |
/// +------------------------------------+
///
/// layer structure
/// +-------------+-----+---------+
/// | name        | idx | parent  |
/// +-------------+-----+---------+
/// | First Layer | 0   | Some(1) |  refers to 'group inside'
/// +-------------+-----+---------+
///
/// cargo test --test layer_and_mask_information_section one_group_inside_another -- --exact
#[test]
fn one_group_inside_another() {
    let psd = include_bytes!("fixtures/groups/green-1x1-one-group-inside-another.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(psd.layers().len(), 1);
    // parent group + children group
    assert_eq!(psd.groups().len(), 2);

    // Check group
    let group = psd.group_by_name("group outside").unwrap();
    assert_eq!(group.id(), TOP_LEVEL_ID);

    // Check subgroup
    let children_group = psd.group_by_name("group inside").unwrap();
    assert_eq!(children_group.parent_id().unwrap(), group.id());

    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());
}

/// PSD file structure
/// group: outside group, parent: `None`
/// 	group: first group inside, parent: `outside group`
/// 		layer: First Layer, parent: `first group inside`
///
/// 	group: second group inside, parent: `outside group`
/// 		group: sub sub group, parent: `second group inside`
/// 			layer: Second Layer, parent: `sub sub group`
///
/// 		layer: Third Layer, parent: `second group inside`
///
/// 	group: third group inside, parent: `outside group`
///
/// 	layer: Fourth Layer, parent: `outside group`
/// layer: Firth Layer, parent: `None`
///
/// group: outside group 2, parent: `None`
/// 	layer: Sixth Layer, parent: `outside group 2`
///
/// cargo test --test layer_and_mask_information_section one_group_with_two_subgroups -- --exact
#[test]
fn one_group_with_two_subgroups() {
    let psd = include_bytes!("fixtures/groups/green-1x1-one-group-with-two-subgroups.psd");
    let psd = Psd::from_bytes(psd).unwrap();

    assert_eq!(6, psd.layers().len());
    assert_eq!(6, psd.groups().len());

    // Check first top-level group
    let outside_group = psd.group_by_name("outside group").unwrap();
    assert_eq!(outside_group.id(), 1);

    // Check first subgroup
    let children_group = psd.group_by_name("first group inside").unwrap();
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    let layer = psd.layer_by_name("First Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());

    // Check second subgroup
    let children_group = psd.group_by_name("second group inside").unwrap();
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    // Check `sub sub group`
    let sub_sub_group = psd.group_by_name("sub sub group").unwrap();
    assert_eq!(sub_sub_group.parent_id().unwrap(), children_group.id());

    let layer = psd.layer_by_name("Second Layer").unwrap();
    assert_eq!(sub_sub_group.id(), layer.parent_id().unwrap());

    let layer = psd.layer_by_name("Third Layer").unwrap();
    assert_eq!(children_group.id(), layer.parent_id().unwrap());

    // Check third subgroup
    let children_group = psd.group_by_name("third group inside").unwrap();
    assert_eq!(children_group.parent_id().unwrap(), outside_group.id());

    let layer = psd.layer_by_name("Fourth Layer").unwrap();
    assert_eq!(outside_group.id(), layer.parent_id().unwrap());

    // Check top-level Firth Group
    let layer = psd.layer_by_name("Firth Layer").unwrap();
    assert_eq!(layer.parent_id(), None);

    // Check second top-level group
    let outside_group = psd.group_by_name("outside group 2").unwrap();
    assert_eq!(outside_group.id(), 6);

    let layer = psd.layer_by_name("Sixth Layer").unwrap();
    assert_eq!(layer.parent_id().unwrap(), outside_group.id());
}

#}

Contributing

This section dives into contributing to the psd crate.

Internal Design

This section discusses the internal design of the psd crate.


Before reading this section it is recommended that you read through the psd specification.

Reading through the spec will bring context to the organization of the codebase and the approach that we take to parsing .psd data


After reading this section you should have a good sense of how everything works and be better prepared to dive into the codebase.

Major Sections

You can think of a Photoshop file as a byte slice &[u8].

These bytes are organized into 5 major sections, each of which have their own sub-sections.

We represent this in our code using the MajorSections type.


# #![allow(unused_variables)]
#fn main() {
// Imported into the book from `src/sections/mod.rs`

pub mod image_data_section;
pub mod image_resources_section;
pub mod layer_and_mask_information_section;

/// References to the different major sections of a PSD file
#[derive(Debug)]
pub struct MajorSections<'a> {
    pub(crate) file_header: &'a [u8],
    pub(crate) color_mode_data: &'a [u8],
    pub(crate) image_resources: &'a [u8],
    pub(crate) layer_and_mask: &'a [u8],
    pub(crate) image_data: &'a [u8],
}

impl<'a> MajorSections<'a> {
    /// Given the bytes of a PSD file, return the slices that correspond to each
    /// of the five major sections.
    ///
    /// ┌──────────────────┐
    /// │   File Header    │
    /// ├──────────────────┤
    /// │ Color Mode Data  │
    /// ├──────────────────┤
    /// │ Image Resources  │
    /// ├──────────────────┤
#}

Our parsing comes down to reading through the bytes in this byte slice and using them to create these five major sections.