Rampa de color bidimensional (matriz de 256×256) interpolada desde 4 colores de esquina

Lo que quiero lograr es crear mediante progtwigción una rampa de color bidimensional representada por una matriz de valores de color de 256×256. El resultado esperado se puede ver en la imagen adjunta. Lo que tengo como punto de partida son los 4 colores de la esquina de la matriz a partir de los cuales se deben interpolar los 254 colores restantes. Si bien tuve cierto éxito en la interpolación de los colores de un eje, el cálculo bidimensional me proporciona algunos dolores de cabeza. Si bien la imagen parece tener un gradiente de color no lineal, estaría feliz con uno lineal.

Si pudiera darme algunos consejos sobre cómo hacer esto con muchas herramientas o con otras herramientas, estaré más que agradecido.

introduzca la descripción de la imagen aquí

Aquí hay una solución súper corta utilizando la función de zoom de scipy.ndimage . Defino una imagen RGB 2×2 con los colores iniciales (aquí los aleatorios) y simplemente la amplío a 256×256, order=1 hace que la interpolación sea lineal. Aquí está el código:

 import numpy as np import matplotlib.pyplot as plt im=(np.random.rand(2,2,3)*255).astype(np.uint8) from scipy.ndimage.interpolation import zoom zoomed=zoom(im,(128,128,1),order=1) plt.subplot(121) plt.imshow(im,interpolation='nearest') plt.subplot(122) plt.imshow(zoomed,interpolation='nearest') plt.show() 

Salida:

salida del guión

Aquí hay 3 formas de hacer esta interpolación bilineal. La primera versión hace toda la aritmética en Python puro, la segunda usa la composición de la imagen PIL , la tercera usa Numpy para hacer la aritmética. Como se esperaba, el Python puro es significativamente más lento que los otros enfoques. La versión Numpy (que se deriva del código escrito por Andras Deak ) es casi tan rápida como la versión PIL para imágenes pequeñas, pero para imágenes más grandes, la versión PIL es notablemente más rápida.

También intenté usar la técnica de escalado de jadsq en PIL, pero los resultados no fueron buenos. Sospecho que el código de interpolación de PIL es un poco defectuoso.

Si desea crear muchas de estas imágenes de gradiente bilineal del mismo tamaño, la técnica PIL tiene otra ventaja: una vez que haya creado las máscaras de composición, no necesita reconstruirlas para cada imagen.

 #!/usr/bin/env python3 ''' Simple bilinear interpolation Written by PM 2Ring 2016.09.14 ''' from PIL import Image from math import floor import numpy as np def color_square0(colors, size): tl, tr, bl, br = colors m = size - 1 r = range(size) def interp_2D(tl, tr, bl, br, x, y): u0, v0 = x / m, y / m u1, v1 = 1 - u0, 1 - v0 return floor(0.5 + u1*v1*tl + u0*v1*tr + u1*v0*bl + u0*v0*br) data = bytes(interp_2D(tl[i], tr[i], bl[i], br[i], x, y) for y in r for x in r for i in (0, 1, 2)) return Image.frombytes('RGB', (size, size), data) # Fastest def color_square1(colors, size): #Make an Image of each corner color tl, tr, bl, br = [Image.new('RGB', (size, size), color=c) for c in colors] #Make the composition mask mask = Image.new('L', (size, size)) m = 255.0 / (size - 1) mask.putdata([int(m * x) for x in range(size)] * size) imgt = Image.composite(tr, tl, mask) imgb = Image.composite(br, bl, mask) return Image.composite(imgb, imgt, mask.transpose(Image.TRANSPOSE)) # This function was derived from code written by Andras Deak def color_square2(colors, size): tl, tr, bl, br = map(np.array, colors) m = size - 1 x, y = np.mgrid[0:size, 0:size] x = x[..., None] / m y = y[..., None] / m data = np.floor(x*y*br + (1-x)*y*tr + x*(1-y)*bl + (1-x)*(1-y)*tl + 0.5) return Image.fromarray(np.array(data, dtype = 'uint8'), 'RGB') color_square = color_square1 #tl = (255, 0, 0) #tr = (255, 255, 0) #bl = (0, 0, 255) #br = (0, 255, 0) tl = (108, 115, 111) tr = (239, 239, 192) bl = (124, 137, 129) br = (192, 192, 175) colors = (tl, tr, bl, br) size = 256 img = color_square(colors, size) img.show() #img.save('test.png') 

salida

gradiente bilineal


Solo por diversión, aquí hay un progtwig GUI simple que usa Tkinter que se puede usar para generar estos gradientes.

 #!/usr/bin/env python3 ''' Simple bilinear colour interpolation using PIL, in a Tkinter GUI Inspired by https://stackoverflow.com/q/39485178/4014959 Written by PM 2Ring 2016.09.15 ''' import tkinter as tk from tkinter.colorchooser import askcolor from tkinter.filedialog import asksaveasfilename from PIL import Image, ImageTk DEFCOLOR = '#d9d9d9' SIZE = 256 #Make the composition masks mask = Image.new('L', (SIZE, SIZE)) m = 255.0 / (SIZE - 1) mask.putdata([int(m * x) for x in range(SIZE)] * SIZE) maskt = mask.transpose(Image.TRANSPOSE) def do_gradient(): imgt = Image.composite(tr.img, tl.img, mask) imgb = Image.composite(br.img, bl.img, mask) img = Image.composite(imgb, imgt, maskt) ilabel.img = img photo = ImageTk.PhotoImage(img) ilabel.config(image=photo) ilabel.photo = photo def set_color(w, c): w.color = c w.config(background=c, activebackground=c) w.img = Image.new('RGB', (SIZE, SIZE), color=c) def show_color(w): c = w.color newc = askcolor(c)[1] if newc is not None and newc != c: set_color(w, newc) do_gradient() def color_button(row, column, initcolor=DEFCOLOR): b = tk.Button(root) b.config(command=lambda w=b:show_color(w)) set_color(b, initcolor) b.grid(row=row, column=column) return b def save_image(): filetypes = [('All files', '.*'), ('PNG files', '.png')] fname = asksaveasfilename(title="Save Image",filetypes=filetypes) if fname: ilabel.img.save(fname) print('Saved image as %r' % fname) else: print('Cancelled') root = tk.Tk() root.title("Color interpolation") coords = ((0, 0), (0, 2), (2, 0), (2, 2)) tl, tr, bl, br = [color_button(r, c) for r,c in coords] ilabel = tk.Label(root, relief=tk.SUNKEN) do_gradient() ilabel.grid(row=1, column=1) b = tk.Button(root, text="Save", command=save_image) b.grid(row=3, column=1) root.mainloop() 

Esta es una forma muy corta de hacerlo con ImageMagick, que se instala en la mayoría de las distribuciones de Linux y está disponible para OSX y Windows. También hay enlaces de Python. De todos modos, justo en la línea de comandos, cree un cuadrado de 2×2 con los colores en las 4 esquinas de su imagen, luego permita que ImageMagick se expanda e interpole hasta el tamaño completo:

 convert \( xc:"#59605c" xc:"#ebedb3" +append \) \ \( xc:"#69766d" xc:"#b3b3a0" +append \) \ -append -resize 256x256 result.png 

introduzca la descripción de la imagen aquí

La primera línea hace un píxel de 1×1 de cada una de sus esquinas superior izquierda y superior derecha y agrega las dos una al lado de la otra. La segunda línea hace un píxel de 1×1 de cada una de las esquinas inferior izquierda e inferior derecha y las agrega una al lado de la otra. La línea final agrega la fila inferior debajo de la fila superior y se amplía por interpolación a 256×256.

Si desea comprender mejor lo que está sucediendo, aquí está la misma imagen básica pero escalada utilizando el vecino más cercano en lugar de la interpolación:

 convert \( xc:"#59605c" xc:"#ebedb3" +append \) \ \( xc:"#69766d" xc:"#b3b3a0" +append \) \ -append -scale 20x20 result.png 

introduzca la descripción de la imagen aquí