views:

615

answers:

1

I'm trying to port some image processing functionality from an Image Magick command (using the Fx Special Effects Image Operator) to Python using PIL. My issue is that I'm not entirely understanding what this fx operator is doing:

convert input.png gradient.png -fx "v.p{0,u*v.h}" output.png

From a high level, this command takes the colors from a gradient image (gradient.png) and applies them as the color palette of the input image (input.png), writing to an output image (output.png).

From what I've figured out, u is the input image, v is the gradient, and it is going through each left-most pixel in the gradient from top to bottom, somehow applying its colors to the input image.

I can't wrap my head around how to do this same thing programmatically with PIL. The best thing I've come up with was to convert the image to a paletted image (down-sampling to a measly 256 colors) and grabbing colors individually from the gradient with a pixel access object.

import Image

# open the input image
input_img = Image.open('input.png')

# open gradient image and resize to 256px height
gradient_img = Image.open('gradient.png')
gradient_img = gradient_img.resize( (gradient_img.size[0], 256,) )

# get pixel access object (significantly quicker than getpixel method)
gradient_pix = gradient_img.load()

# build a sequence of 256 palette values (going from bottom to top)
sequence = []
for i in range(255, 0, -1):
    # from rgb tuples for each pixel row
    sequence.extend(gradient_pix[0, i])

# convert to "P" mode in order to use putpalette() with built sequence
output_img = input_img.convert("P")
output_img.putpalette(sequence)

# save output file
output_img = output_img.convert("RGBA")
output_img.save('output.png')

This works, but like I said, it down-samples to 256 colors. Not only is this a ham-handed way of doing things, it results in a really crappy output image. How could I duplicate the Magick functionality without cramming the result into 265 colors?

addendum: forgot to cite the blog where I found the original Magick command

+1  A: 

I know it's been about a month and you might has already figured it out. But here is the answer.

From ImageMagicK documentation I was able to understand what the effect is actually doing.

convert input.png gradient.png -fx "v.p{0,u*v.h}" output.png

v is the second image (gradient.png)
u is the first image (input.png)
v.p will get a pixel value
v.p{0, 0} -> first pixel in the image
v.h -> the hight of the second image
v.p{0, u * v.h} -> will read the Nth pixel where N = u * v.h

I converted that into PIL, and the result looks exactly like you want it to be:

import Image

# open the input image
input_img = Image.open('input.png')

# open gradient image and resize to 256px height
gradient_img = Image.open('gradient.png')
gradient_img = gradient_img.resize( (gradient_img.size[0], 256,) )

# get pixel access object (significantly quicker than getpixel method)
gradient_pix = gradient_img.load()

data = input_img.getdata()
input_img.putdata([gradient_pix[0, r] for (r, g, b, a) in data])
input_img.save('output.png')
Nadia Alramli
thanks, that works beatifully! i'd figured out a similar algorithm (with much banging of head and keyboard, and lots of help from the imagemagick folk), but I think yours might actually be more efficient than mine anyway
EvanK