0

I'm trying to adjust my Image's shadows, midtones, and highlights separately with PIL. I'm currently selecting these based on a division of 255 total by 3 (e.g. if(i<85) for shadows, if(i >= 85 and i < 170) for midtones, etc.).

This is the input image: Input Image

The result is currently very aliased. Results

Here are the Photoshop Levels Settings for the Desired Output: Levels

I've tried resizing the individual channels by twice the size, then resizing again with resample=Image.ANTIALIAS, but that didn't produce a good effect.

self.shadows,self.midtones, self.highlights, and self.alpha are a float property value to control the adjustment.

pil_image.convert('RGBA')

# Split into 4 channels
r, g, b, a = pil_image.split()

shadow_r = r.point(lambda i: i * self.shadows if(i < 85) else 0)
shadow_g = g.point(lambda i: i * self.shadows if(i < 85) else 0)
shadow_b = b.point(lambda i: i * self.shadows if(i < 85) else 0)
shadow_a = a.point(lambda i: i * self.alpha if(i < 85) else 0)

mid_r = r.point(lambda i: i * self.midtones if(i >= 85 and i < 170) else 0)
mid_g = g.point(lambda i: i * self.midtones if(i >= 85 and i < 170) else 0)
mid_b = b.point(lambda i: i * self.midtones if(i >= 85 and i < 170) else 0)
mid_a = a.point(lambda i: i * self.alpha if(i >= 85 and i < 170) else 0)

highlight_r = r.point(lambda i: i * self.highlights if(i >= 170) else 0)
highlight_g = g.point(lambda i: i * self.highlights if(i >= 170) else 0)
highlight_b = b.point(lambda i: i * self.highlights if(i >= 170) else 0)
highlight_a = a.point(lambda i: i * self.alpha if(i >= 170) else 0)

# Recombine back to RGB image
shadow_img = Image.merge('RGBA', (shadow_r, shadow_g, shadow_b, shadow_a))
mid_img = Image.merge('RGBA', (mid_r, mid_g, mid_b, mid_a))
highlight_img = Image.merge('RGBA', (highlight_r, highlight_g, highlight_b, highlight_a))

pil_image2 = ImageChops.add(shadow_img,mid_img)
pil_image = ImageChops.add(pil_image2,highlight_img)

Can anyone please guide me on how to adjust the channels with a better falloff for the selection? Thank you!

  • I presume the image you have provided is the result you obtain. Please provide the input image and the result you are expecting - maybe produced using other means such as Photoshop. Is there some reference paper, or description or basis for the algorithm you are trying to use? – Mark Setchell Jul 23 '23 at 08:22
  • @MarkSetchell, thank you for the reply. I have updated my question for clarity and included the input image. There is no reference paper or algorithm: I'm just trying to match the results I get in Photoshop's Levels command. In the Desired Results image, I'm simply grabbing the left shadow knot on the histogram and dragging it to the right (but I'd want this to work for Midtones and Highlights too). – Dr. Pontchartrain Jul 23 '23 at 09:01
  • If you can show the Photoshop dialog box set up correspondingly, that should help. – Mark Setchell Jul 23 '23 at 09:03
  • those value ranges must not have hard edges. you need to do this with a smooth curve. at the very least, it needs to be continuous. you have discontinuities in the piecewise definition of your mapping – Christoph Rackwitz Jul 23 '23 at 09:22
  • @ChristophRackwitz Thank you for your message. How might I go about generating the curve? – Dr. Pontchartrain Jul 23 '23 at 09:24
  • @MarkSetchell OP's function isn't continuous. there's nothing wrong with it being piecewise. there is everything wrong with it not being continuous. – Christoph Rackwitz Jul 23 '23 at 10:54
  • @ChristophRackwitz Yes, I agree. I was merely saying that the Photoshop dialog box shown doesn't require a piecewise function - it is a simple straight line. – Mark Setchell Jul 23 '23 at 11:09

1 Answers1

1

I'm not sure why you have a piecewise function implemented in your code for the different parts of your curve. The Photoshop Levels can be rendered equivalently with the following Curve:

enter image description here

As PIL applies the point() function to all channels, there is no need to split and recombine, you can just process all the channels in one go with:

result = im.point(lambda i: (i-46)*255/(255-46) if(i > 46) else 0)

enter image description here

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Thank you so much for this! It looks really promising. How would I use this code to edit the midtones and highlights? `highlight = im.point(lambda i: (i-46)*255/(255-46) if(i >= 170) else 0)`? I'd like the user to be edit these with three sliders simultaneously. Would I be able to combine the separate edits as I was before with the add blend mode? Thank you! – Dr. Pontchartrain Jul 23 '23 at 17:50
  • The 46 in my formula comes from the position of the shadow slider in your dialog box. If you want to do something different for the highlights (i.e. above 170) you'll need to change every instance of 46 into 170 in the formula. – Mark Setchell Jul 23 '23 at 18:46
  • You might find it easier to apply a *"Look-Up Table"*, i.e. LUT like this https://stackoverflow.com/a/50602577/2836621 At the moment, I still can't see what actual adjustment you want to make in its entirety. – Mark Setchell Jul 23 '23 at 18:52
  • Thank you for your answers and insight. I tried what you described in your formula, but saw the aliasing again with self.highlights = 104): `result = im.point(lambda i: (i+self.highlights)*255/(255-self.highlights) if(i > self.highlights) else i)` https://snipboard.io/FWbidk.jpg – Dr. Pontchartrain Jul 23 '23 at 19:19
  • As for what adjustment I'm trying to make: I'm making an Image Editor and looking to recreate the three knot adjustments that are made in the Photoshop Levels dialog. https://youtu.be/Mu_p1tE9ZCg – Dr. Pontchartrain Jul 23 '23 at 19:19