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

Add visible, opacity and blending mode support #15

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 109 additions & 43 deletions src/blend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ use std::cmp::min;

use crate::sections::layer_and_mask_information_section::layer::BlendMode;

// Converts layer opacity property to alpha channel
pub(crate) fn perform_opacity(pixel: &mut [u8; 4], opacity: u8) {
// Multiplies the pixel's current alpha by the passed in `opacity`
pub(crate) fn apply_opacity(pixel: &mut [u8; 4], opacity: u8) {
let alpha = opacity as f32 / 255.;
pixel[3] = (pixel[3] as f32 * alpha) as u8;
}

/// blend the two pixels.
///
/// TODO: Take the layer's blend mode into account when blending layers. Right now
///
/// https://www.w3.org/TR/compositing-1/#simplealphacompositing
/// `Cs = (1 - αb) x Cs + αb x B(Cb, Cs)`
Expand Down Expand Up @@ -40,7 +37,7 @@ pub(crate) fn blend_pixels(top: [u8; 4], bottom: [u8; 4], blend_mode: BlendMode,
let alpha_b = bottom[3] as f32 / 255.;
let alpha_output = alpha_s + alpha_b * (1. - alpha_s);

let (r_s, g_s, b_s) = (top[0] as f32 / 255., top[1] as f32 / 255., top[2] as f32 / 255.);
let (r_s, g_s, b_s) = (top[0] as f32 / 255., top[1] as f32 / 255., top[2] as f32 / 255.);
let (r_b, g_b, b_b) = (bottom[0] as f32 / 255., bottom[1] as f32 / 255., bottom[2] as f32 / 255.);

let blend_f = map_blend_mode(blend_mode);
Expand All @@ -60,21 +57,40 @@ type BlendFunction = dyn Fn(f32, f32) -> f32;

/// Returns blend function for given BlendMode
fn map_blend_mode(blend_mode: BlendMode) -> &'static BlendFunction {
Copy link
Owner

Choose a reason for hiding this comment

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

🔥 🔥 🔥 🔥 🔥

// Modes are sorted like in Photoshop UI
match blend_mode {
BlendMode::Normal => &normal,
BlendMode::Multiply => &multiply,
BlendMode::Screen => &screen,
BlendMode::Overlay => &overlay,
// Here we need Dissolve
Copy link
Owner

Choose a reason for hiding this comment

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

Should we instead add BlendMode::Dissolve => unimplemented!() etc for all of these?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It can be BlendMode::Dissolve => &dissolve
and

fn dissolve(color_b: f32, color_s: f32) -> f32 {
    unimplemented!()
}

Copy link
Owner

Choose a reason for hiding this comment

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

Even betteer

// --------------------------------------
BlendMode::Darken => &darken,
BlendMode::Multiply => &multiply,
BlendMode::ColorBurn => &color_burn,
BlendMode::LinearBurn => &linear_burn,
// Here we need Darker Color
// --------------------------------------
BlendMode::Lighten => &lighten,
BlendMode::Screen => &screen,
BlendMode::ColorDodge => &color_dodge,
BlendMode::ColorBurn => &color_burn,
BlendMode::LinearDodge => &linear_dodge,
BlendMode::LinearBurn => &linear_burn,
BlendMode::HardLight => &hard_light,
// Here we need Lighter Color
// --------------------------------------
BlendMode::Overlay => &overlay,
BlendMode::SoftLight => &soft_light,
BlendMode::HardLight => &hard_light,
// Here we need Vivid Light
// Here we need Linear Light
// Here we need Pin Light
// Here we need Hard Mix
// --------------------------------------
BlendMode::Difference => &difference,
BlendMode::Exclusion => &exclusion,
BlendMode::Subtract => &subtract,
BlendMode::Divide => &divide,
// --------------------------------------
// Here we need Hue
// Here we need Saturation
// Here we need Color
// Here we need Luminosity

// TODO: make other modes
_ => unimplemented!()
Expand All @@ -101,6 +117,31 @@ fn multiply(color_b: f32, color_s: f32) -> f32 {
color_b * color_s
}

/// https://helpx.adobe.com/photoshop/using/blending-modes.html
///
/// Looks at the color information in each channel and divides the blend color from the base color.
/// In 8- and 16-bit images, any resulting negative values are clipped to zero.
///
/// `B(Cb, Cs) = Cb / Cs`
#[inline(always)]
fn divide(color_b: f32, color_s: f32) -> f32 {
if color_s == 0. {
color_b
} else {
color_b / color_s
}
}

/// https://helpx.adobe.com/photoshop/using/blending-modes.html
///
/// Looks at the color information in each channel and subtracts the blend color from the base color.
///
/// `B(Cb, Cs) = Cb - Cs`
#[inline(always)]
fn subtract(color_b: f32, color_s: f32) -> f32 {
(color_b - color_s).max(0.)
}

/// https://www.w3.org/TR/compositing-1/#blendingscreen
/// Multiplies the complements of the backdrop and source color values, then complements the result.
///
Expand All @@ -119,7 +160,7 @@ fn screen(color_b: f32, color_s: f32) -> f32 {
///
/// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged.
///
/// `B(Cb, Cs) = max(Cb, Cs)`
/// `B(Cb, Cs) = min(Cb, Cs)`
#[inline(always)]
fn darken(color_b: f32, color_s: f32) -> f32 {
color_b.min(color_s)
Expand Down Expand Up @@ -166,20 +207,16 @@ fn color_dodge(color_b: f32, color_s: f32) -> f32 {
/// ```ignore
/// if(Cb == 1)
/// B(Cb, Cs) = 1
/// else if(Cs == 0)
/// B(Cb, Cs) = 0
/// else
/// B(Cb, Cs) = 1 - min(1, (1 - Cb) / Cs)
/// B(Cb, Cs) = max(0, (1 - (1 - Cs) / Cb))
///```
#[inline(always)]
fn color_burn(color_b: f32, color_s: f32) -> f32 {
let r = if color_b == 1. {
if color_b == 1. {
1.
} else if color_s == 0. {
0.
} else {
((1. - color_b) / color_s).min(1.)
};
(1. - (1. - color_s) / color_b).max(0.)
}
}

/// See: http://www.simplefilter.de/en/basics/mixmods.html
Expand All @@ -199,11 +236,19 @@ fn linear_dodge(color_b: f32, color_s: f32) -> f32 {
/// The tonal values of fore- and background that sum up to less than 255 (i.e. 1.0) become pure black.
/// If the foreground image A is converted prior to the operation, the result is the mathematical subtraction.
///
/// Also: Subtract
/// ```ignore
/// sum = Cb + Cs
/// if (sum >= 1)
/// B(Cb, Cs) = Cb - (1 - Cs)
/// else
/// B(Cb, Cs) = 0
/// ```
/// Also:
/// `B(Cb, Cs) = Cb + Cs - 1`
#[inline(always)]
fn linear_burn(color_b: f32, color_s: f32) -> f32 {
let sum = color_b + color_s;

if sum >= 1. {
color_b - (1. - color_s)
} else {
Expand All @@ -224,27 +269,6 @@ fn overlay(color_b: f32, color_s: f32) -> f32 {
hard_light(color_s, color_b) // inverted hard_light
}

/// https://www.w3.org/TR/compositing-1/#blendinghardlight
///
/// Multiplies or screens the colors, depending on the source color value.
/// The effect is similar to shining a harsh spotlight on the backdrop.
///
/// ```ignore
/// if(Cs <= 0.5)
/// B(Cb, Cs) = Multiply(Cb, 2 x Cs) = 2 x Cb x Cs
/// else
/// B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
/// ```
/// See the definition of `multiply` and `screen` for their formulas.
#[inline(always)]
fn hard_light(color_b: f32, color_s: f32) -> f32 {
if color_s < 0.5 {
multiply(color_b, 2. * color_s)
} else {
screen(color_b, 2. * color_s - 1.)
}
}

/// https://www.w3.org/TR/compositing-1/#blendingsoftlight
///
/// Darkens or lightens the colors, depending on the source color value.
Expand Down Expand Up @@ -279,6 +303,48 @@ fn soft_light(color_b: f32, color_s: f32) -> f32 {
}
}

/// https://www.w3.org/TR/compositing-1/#blendinghardlight
///
/// Multiplies or screens the colors, depending on the source color value.
/// The effect is similar to shining a harsh spotlight on the backdrop.
///
/// ```ignore
/// if(Cs <= 0.5)
/// B(Cb, Cs) = Multiply(Cb, 2 x Cs) = 2 x Cb x Cs
/// else
/// B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
/// ```
/// See the definition of `multiply` and `screen` for their formulas.
#[inline(always)]
fn hard_light(color_b: f32, color_s: f32) -> f32 {
if color_s < 0.5 {
multiply(color_b, 2. * color_s)
} else {
screen(color_b, 2. * color_s - 1.)
}
}

/// https://www.w3.org/TR/compositing-1/#blendinghardlight
///
/// Multiplies or screens the colors, depending on the source color value.
/// The effect is similar to shining a harsh spotlight on the backdrop.
///
/// ```ignore
/// if(Cs <= 0.5)
/// B(Cb, Cs) = Multiply(Cb, 2 x Cs) = 2 x Cb x Cs
/// else
/// B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
/// ```
/// See the definition of `multiply` and `screen` for their formulas.
#[inline(always)]
fn vivid_light(color_b: f32, color_s: f32) -> f32 {
if color_s < 0.5 {
multiply(color_b, 2. * color_s)
} else {
screen(color_b, 2. * color_s - 1.)
}
}

/// https://www.w3.org/TR/compositing-1/#blendingdifference
///
/// Subtracts the darker of the two constituent colors from the lighter color.
Expand Down
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,10 @@ impl Psd {
let mut copy = [0; 4];
copy.copy_from_slice(pixel);

blend::perform_opacity(&mut copy, layer.opacity);
blend::apply_opacity(&mut copy, layer.opacity);
copy
};

println!("layer: {:?}, opacity: {:?}", layer.name(), layer.opacity);
// This pixel is fully opaque, return it
let pixel = if pixel[3] == 255 && layer.opacity == 255 {
pixel
Expand All @@ -328,7 +327,6 @@ impl Psd {
);

blend::blend_pixels(pixel, pixel_below, layer.blend_mode, &mut final_pixel);
println!("mode: {:?}, pixel: {:?}, pixel_below: {:?}, final: {:?}", layer.blend_mode, pixel, pixel_below, final_pixel);
final_pixel
} else {
// There is no pixel below this layer, so use it even though it has transparency
Expand Down
1 change: 0 additions & 1 deletion src/sections/layer_and_mask_information_section/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,6 @@ impl GroupDivider {

/// BlendMode represents blending mode.
Copy link
Owner

Choose a reason for hiding this comment

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

Describes how to blend a layer with the layer below it

#[derive(Debug, Clone, Copy)]
///
pub enum BlendMode {
PassThrough = 0,
Normal = 1,
Expand Down
3 changes: 1 addition & 2 deletions src/sections/layer_and_mask_information_section/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,6 @@ fn read_layer_record(cursor: &mut PsdCursor) -> Result<LayerRecord, Error> {

let opacity = cursor.read_u8()?;

// We do not currently parse the clipping, skip it
let clipping_base = cursor.read_u8()?;
let clipping_base = clipping_base == 0;

Expand All @@ -379,7 +378,7 @@ fn read_layer_record(cursor: &mut PsdCursor) -> Result<LayerRecord, Error> {
// - bit 2 = obsolete;
// - bit 3 = 1 for Photoshop 5.0 and later, tells if bit 4 has useful information;
// - bit 4 = pixel data irrelevant to appearance of document
let visible = cursor.read_u8()? & (1 << 4) != 0;
let visible = cursor.read_u8()? & (1 << 1) != 0; // here we get second bit - visible

// We do not currently parse the filter, skip it
cursor.read_1()?;
Expand Down
Loading