skip to Main Content

Most image editing software has gradient map functions. Gradient maps take the brightness of a given pixel and apply a gradient of colors according to brightness. Photoshop and other software have ways to automate this, but they can’t do exactly what I want, so I thought Python might do the trick. Unfortunately I’m having a very hard time understanding or applying any of the results that come up when I search for gradient maps or color maps with Python.

Gradient maps

All the potential solution threads I found used numpy or matplotlib which have lots of mathy lines that go right over my head… I would love some help with this. Initially I had something going with Processing, but I found the task of exporting tons of images with Processing to be weird and hacky. Plus I like Python, and want to learn how to edit and generate art with it.

This is what I’m working with right now.

from PIL import Image

myPalette = ['#1A1423', '#684756', '#AB8476']

def colorMap(pixel, palette): 
    # Calculate the brightness of pixel 
    R, G, B = pixel 
    brightness = sum([R, G, B])/3
 
    # Map gradient of colors to brightness
    # ???...
    
    return mappedColor

 
img = Image.open('image_input.png') 
pixels = img.load()
 
for x in range(img.size[0]): 
    for y in range(img.size[1]): 
        pixel = img.getpixel((x, y)) 
        pixels[x, y] = colorMap(pixel, myPalette)
 
img.save('image_output.png')

Loading, calculating brightness, and saving are easy. I just have no idea how to apply a gradient of my palette to the pixel.

4

Answers


  1. I know you said you didn’t want matplotlib, but I don’t think you need mathy lines to make it work. I tested it with a PNG of my own, so hopefully it works for yours as well. Here’s an example that draws heavily on this documentation.

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.colors import LinearSegmentedColormap
    
    # Read PNG and take average:
    img = plt.imread('image_input.png')
    Z = img.mean(-1)
    
    #   Kestrel
    color_list = [np.array([207,117,80])/255,
                  np.array([219,179,127])/255,
                  np.array([248,249,251])/255,
                  np.array([185,191,205])/255,
                  np.array([88,104,127])/255]
    
    cmap_name = 'Kestrel'
    N_bin=100
    # Create the colormap
    cmap = LinearSegmentedColormap.from_list(cmap_name, color_list, N=N_bin)
    # Fewer bins will result in "coarser" colomap interpolation
    fig, ax = plt.subplots()
    im = ax.imshow(Z, cmap=cmap)
    fig.colorbar(im, ax=ax)
    
    plt.show()
    
    Login or Signup to reply.
  2. You can do that quite easily with ImageMagick or with PIL/Numpy/OpenCV.

    The first thing is to get hold of the colormap – the vertical bar down the right side of your image. I don’t have or know Clip Studio, so maybe it will let you export the colormap, or create a greyscale gradient and apply the colormap to it, then save the result as a PNG. In my case, I loaded your image into Photoshop, cut out the gradient and rotated it to make an image exactly 256-pixels wide by 1-pixel high. Enlarged, that looks like this:

    colourmap.png

    enter image description here

    I also cropped your swirl thing off the left side of your image – please post images separately in future.

    swirl.png

    enter image description here


    Now for applying it. First, just with ImageMagick in Terminal. I loaded your swirl image and separated it into its constituent RGB channels then averaged the channels and applied the colourmap, also known as CLUT or "Colour Lookup Table":

    magick swirl.png -channel RGB -separate -evaluate-sequence mean colourmap.png -clut result.png
    

    enter image description here


    Next, same thing with PIL/Numpy:

    #!/usr/bin/env python3
    
    import numpy as np
    from PIL import Image
    
    # Load image, make into Numpy array and average RGB channels
    im = Image.open('swirl.png').convert('RGB')
    na = np.array(im)
    grey = np.mean(na, axis=2).astype(np.uint8)
    Image.fromarray(grey).save('DEBUG-grey.png')   # DEBUG only
    
    # Load colourmap
    cmap = Image.open('colourmap.png').convert('RGB')
    
    # Make output image, same height and width as grey image, but 3-channel RGB
    result = np.zeros((*grey.shape,3), dtype=np.uint8)
    
    # Take entries from RGB colourmap according to greyscale values in image
    np.take(cmap.getdata(), grey, axis=0, out=result)
    
    # Save result
    Image.fromarray(result).save('result.png')
    

    You can also generate piece-wise linear colormaps like this:

     magick -size 160x1 gradient:navy-"rgb(220,110,110)"   
            -size  60x1 gradient:"rgb(220,110,110)"-yellow 
            -size  35x1 gradient:yellow-white              
            +append colourmap.png
    

    enter image description here

    That makes three segments each with a linear gradient:

    • 160×1 in navy to salmon,
    • 60×1 in salmon to yellow and
    • 35×1 in yellow to white

    then appends them together.


    If you make all the segments of the colour map the same length, you will get a different interpretation:

    magick -size 85x1 
       gradient:navy-"rgb(220,110,110)"   
       gradient:"rgb(220,110,110)"-yellow 
       gradient:yellow-white +append -resize 256x1! colourmap.png
    

    enter image description here

    That leads to this:

    enter image description here

    Login or Signup to reply.
  3. The trick is to define a mapping table that associates a color to every gray value in the range [0, 255] (in your case, you can even map [0, 255 x 3] by not taking the average).

    Now to get a smooth effect, you need to change the color components regularly. The options are unlimited. Here is a typical example:

    enter image description here

    enter image description here

    Login or Signup to reply.
  4. Here are two methods, one using Matplotlib and one using only OpenCV

    Method #1: OpenCV + matplotlib.pyplot.get_cmap

    We first load in the image as grayscale. By default, OpenCV reads in an image as 3-channel, 8-bit BGR. We can directly load in an image as grayscale using cv2.imread() with the cv2.IMREAD_GRAYSCALE parameter or use cv2.cvtColor() to convert a BGR image to grayscale with the cv2.COLOR_BGR2GRAY parameter. Once we load in the image, we throw this grayscale image into Matplotlib to obtain our heatmap image. Matplotlib returns a RGB format so we must convert back to Numpy format and switch to BGR colorspace for use with OpenCV. Here’s a example using the inferno colormap. See choosing color maps in Matplotlib for available built-in colormaps depending on your desired use case.

    Input image -> Heatmap image


    Code

    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    
    image = cv2.imread('1.png', 0)
    colormap = plt.get_cmap('inferno')
    heatmap = (colormap(image) * 2**16).astype(np.uint16)[:,:,:3]
    heatmap = cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)
    
    cv2.imshow('image', image)
    cv2.imshow('heatmap', heatmap)
    cv2.waitKey()
    

    Method #2: cv2.applyColorMap()

    We can use OpenCV’s built in heatmap function. Here’s the result using the cv2.COLORMAP_HOT heatmap

    Code

    import cv2
    
    image = cv2.imread('1.png', 0)
    heatmap = cv2.applyColorMap(image, cv2.COLORMAP_HOT)
    
    cv2.imshow('heatmap', heatmap)
    cv2.waitKey()
    

    Note: Although OpenCV’s built-in implementation is short and quick, I recommend using Method #1 since there is a larger colormap selection. Matplotlib has hundreds of various colormaps and allows you to create your own custom color maps while OpenCV only has 12 to choose from. Here’s the built in OpenCV colormap selection:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search