bolero
bolero

Reputation: 311

How to write image with palette information?

I want to create a PNG image file with palette information in Python using Pillow and/or pypng.

The input is:

  1. Palette information

    [[0, 128, 0],
     [0, 64, 128],
     [0, 128, 128],
     [0, 64, 0],
     [0, 64, 64],
     [128, 128, 0],
     ...
    ]
    
  2. Input image (numpy.ndarray)

    img = cv2.imread("myimage.png")
    print(img)
    
    [[[0, 128, 0],
      [0, 128, 0],
      [0, 128, 0],
      ...
     ]
     [[0, 128, 0],
      [0, 64, 64],
      [0, 64, 0],
      ...
     ]
    ]
    

And, the output is:

image = PIL.Image.open("output.png")
image = np.array(image)
print(image)

[[0, 0, 0, 0, ..... 5, 5, 5]
 [0, 4, 3, 3, ..... 4, 4, 4]
  ...
]

The input image and output image must be visually identical,

After reading the output image with PIL.Image.open and changing it to a NumPy array, it should be output as above.

Is there a way to accomplish that?

Upvotes: 1

Views: 2753

Answers (1)

HansHirse
HansHirse

Reputation: 18895

Here's some demonstration code to convert an existing RGB image to some indexed color image. Please keep in mind, that Pillow only allows storing 256 different colors in some color palette, cf. Image.putpalette. So, make sure to have your input images not containing more than 256 different colors.

Also, I will assume, that the palette is known before, and that all colors in the existing RGB image are exclusively from that palette. Otherwise, you'd need to add code for extracting all colors, and setting up a proper palette beforehand.

import cv2
import numpy as np
from PIL import Image

# Existing palette as nested list
palette = [
    [0, 128, 0],
    [0, 64, 128],
    [0, 128, 128],
    [0, 64, 0],
]

# Existing RGB image, read with OpenCV (Attention: Correct color ordering)
img = cv2.imread('myimage.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w = img.shape[:2]
print(img)
# [[[  0 128   0]
#   [  0 128   0]
#   [  0 128   0]
#   ...
#   [  0 128 128]
#   [  0 128 128]
#   [  0 128 128]]

# Generate grayscale output image with replaced values
img_pal = np.zeros((h, w), np.uint8)
for i_p, p in enumerate(palette):
    img_pal[np.all(img == p, axis=2)] = i_p
cv2.imwrite('output.png', img_pal)

# Read grayscale image with Pillow
img_pil = Image.open('output.png')
print(np.array(img_pil))
# [[0 0 0 ... 2 2 2]
#  [0 0 0 ... 2 2 2]
#  [0 0 0 ... 2 2 2]
#  ...
#  [1 1 1 ... 3 3 3]
#  [1 1 1 ... 3 3 3]
#  [1 1 1 ... 3 3 3]]

# Convert to mode 'P', and apply palette as flat list
img_pil = img_pil.convert('P')
palette = [value for color in palette for value in color]
img_pil.putpalette(palette)

# Save indexed image for comparison
img_pil.save('output_indexed.png')

That's the existing RGB image myimage.png:

Input

That's the intermediate output.png – you most likely won't see the different very dark gray, nearly black colors:

Output

For comparison, that's the indexed color image, after converting to mode P, and applying the palette:

Indexed output

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.19041-SP0
Python:        3.9.1
PyCharm:       2021.1.1
NumPy:         1.19.5
OpenCV:        4.5.2
Pillow:        8.2.0
----------------------------------------

Upvotes: 2

Related Questions