Pintar por números (usando programação, não números)

56

Sua tarefa é criar um programa que obtenha uma imagem destacada em preto e branco (imagens de exemplo estão abaixo) e a preencha com cores. Depende de você como separar cada região e qual a cor a ser preenchida (você pode até usar um RNG).

Por exemplo:

saída por exemplo 1

Como você pode ver, eu sou claramente um artista de calibre superior quando se trata de MS Paint.


Pontuação

Como é um concurso de popularidade, ganha a resposta com o maior número de votos. Os eleitores são incentivados a julgar as respostas

  • Critério de entrada: qualquer imagem que consiste em fundo branco / cinza claro e contornos preto / cinza escuro
  • Quão bem a coloração é feita; significando que poucas ou nenhuma área é branca, diferentemente do descrito acima (a menos que você obviamente pretenda usar branco, por exemplo, para nuvens)
  • Personalização das cores usadas em determinadas seções
  • Quão bem o sistema funciona em uma variedade de imagens diferentes (com detalhes variados)
  • Poste quanto tempo seu programa leva por imagem. Podemos não estar jogando código de golfe, mas um código mais curto, mais rápido e mais eficiente deve ser considerado melhor
  • Deve exibir a nova imagem na tela ou em um arquivo (não superior a 2 MB, para que possa ser mostrada na resposta)
  • Justifique por que você escolheu enviar para esse tipo de imagem e comente / explique o funcionamento do seu código
  • A aplicabilidade da cor usada à respectiva forma é limitada (esquema de cores realista, isto é, grama é verde, cercas de madeira são marrons etc.)

    "Eu poderia colorir aleatoriamente cada área, mas se eu pudesse identificar a" cerca "e torná-la com a mesma cor, isso é algo que merece votos positivos". - NathanMerrill

Como este é um concurso de popularidade, você também pode opcionalmente julgar por:

  • Apelo geral (quão boa é a imagem)
  • Talento artístico; se você pode programar em sombreamento ou cores no estilo aquarela etc.

Em geral, a menor imagem gerada (tamanho do arquivo) da mais alta qualidade, com o programa em jejum e a votação pública mais alta, vencerá.

Se você tiver outras especificações de julgamento que acha que devem ser usadas, recomende-as nos comentários desta postagem.


Exemplos

Não tenho nada; todas as imagens de exemplo são de uma licença de creative commons.

exemplo 1 em preto / branco Fonte: https://pixabay.com/ro/stejar-arbore-schi%C5%A3%C4%83-natura-303890/ exemplo 2 em preto / branco Fonte: http://www.freestockphotos.biz/stockphoto/10665 exemplo 3 em preto / branco Fonte: http: / /crystal-rose1981.deviantart.com/art/Dragon-Tattoo-Outline-167320011 exemplo 4 em preto / branco Fonte: http://jaclynonacloudlines.deviantart.com/art/Gryphon-Lines-PF-273195317 exemplo 5 em preto / branco Fonte: http://captaincyprus.deviantart.com / art / Dragon-OutLine-331748686 exemplo 6 em preto / branco Fonte: http://electric-meat.deviantart.com/art/A-Heroes-Farewell-280271639 exemplo 7 em preto / branco Fonte: http://movillefacepalmplz.deviantart.com/art/Background-The-Pumpkin -Fazenda-dos-velhos-dias-342865938


EDIT: Devido à suavização de serrilhado nas linhas, causando pixels não preto / branco e algumas imagens que podem conter cinza em vez de preto / branco, como um desafio bônus, você pode tentar lidar com isso. Deve ser fácil o suficiente na minha opinião.

OliverGriffin
fonte
4
Para todos: por favor, não downvote / fechar isso como um "concurso de arte" - há mais do que isso
edc65
16
Bem-vindo ao PPCG! Aplaudo por ter tido a coragem de não apenas ter seu primeiro post como um desafio, e não apenas um desafio pop-con, mas um desafio artístico em cima de tudo. Boa sorte, desejo-lhe o melhor e, se você ficar por aqui, acho que estará indo muito longe aqui.
AdmBorkBork
4
@OliverGriffin Estou votando contra o fechamento e também adicionei as imagens que você vinculou para você. Você pode remover os comentários, se desejar.
Addison Crump
2
Finalmente, encontrei uma abordagem que provavelmente não sobrecarregaria a pilha, mas agora está funcionando devagar.
precisa saber é o seguinte
4
Votei para reabrir sua pergunta e alterei meu -1 para +1. Boa edição de trabalho e adição de informações adicionais. Aplaudo-o também por ser tão receptivo às críticas da comunidade. Bem-vindo ao PPCG! Espero que goste.
Zach Gates

Respostas:

30

Aerografia espectral (Python, PIL, scipy)

Isso usa um algoritmo matemático sofisticado para produzir bobagens coloridas. O algoritmo está relacionado ao algoritmo PageRank do Google, mas para pixels em vez de páginas da web.

Eu adotei essa abordagem porque achava que, diferentemente dos métodos baseados em preenchimento de inundação, ele pode lidar com imagens como a galinha e a árvore, onde existem formas que não são totalmente delimitadas por linhas pretas. Como você pode ver, funciona meio que, embora também tenda a cores em diferentes partes do céu em cores diferentes

Para os que pensam matematicamente: o que está fazendo é essencialmente construir o gráfico de adjacência dos pixels while na imagem e, em seguida, encontrar os 25 principais vetores próprios do gráfico Laplaciano. (Exceto que não é bem isso, porque incluímos os pixels escuros, apenas damos um peso menor às conexões. Isso ajuda a lidar com o antialiasing e também parece dar melhores resultados em geral.) Tendo encontrado os autovetores, ele cria um combinação linear aleatória deles, ponderada pelos seus autovalores inversos, para formar os componentes RGB da imagem de saída.

No interesse do tempo de computação, a imagem é reduzida antes de fazer tudo isso, depois é reduzida novamente e depois multiplicada pela imagem original. Ainda assim, ele não roda rapidamente, levando entre 2 e 10 minutos na minha máquina, dependendo da imagem de entrada, embora por algum motivo o frango demore 17 minutos.

Na verdade, pode ser possível transformar essa ideia em algo útil, criando um aplicativo interativo onde você pode controlar a cor e a intensidade de cada um dos vetores próprios. Dessa forma, você pode desbotar os que dividem o céu em seções diferentes e desbotar os que captam os recursos relevantes da imagem. Mas eu não tenho planos de fazer isso sozinho :)

Aqui estão as imagens de saída:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

(Não funcionou tão bem nas abóboras, então eu o omito.)

E aqui está o código:

import sys
from PIL import Image
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as spl
import os
import time

start_time = time.time()

filename = sys.argv[1]
img = Image.open(filename)
orig_w, orig_h = img.size

# convert to monochrome and remove any alpha channel
# (quite a few of the inputs are transparent pngs)
img = img.convert('LA')
pix = img.load()
for x in range(orig_w):
    for y in range(orig_h):
        l, a = pix[x,y]
        l = (255-a) + a*l/255
        a = 255
        pix[x,y] = l,a
img = img.convert('L')

orig_img = img.copy()

# resize to 300 pixels wide - you can get better results by increasing this,
# but it takes ages to run
orig_w, orig_h = img.size
print "original size:", str(orig_w)+ ', ' + str(orig_h)
new_w = 300
img = img.resize((new_w, orig_h*new_w/orig_w), Image.ANTIALIAS)

pix = img.load()
w, h = img.size
print "resizing to", str(w)+', '+str(h)

def coords_to_index(x, y):
    return x*h+y

def index_to_coords(i):
    return (int(i/h), i%h)

print "creating matrix"

A = sp.lil_matrix((w*h,w*h))

def setlink(p1x, p1y, p2x, p2y):
    i = coords_to_index(p1x,p1y)
    j = coords_to_index(p2x,p2y)
    ci = pix[p1x,p1y]/255.
    cj = pix[p2x,p2y]/255.
    if ci*cj > 0.9:
        c = 1
    else:
        c =  0.01
    A[i,j] = c
    return c

for x in range(w):
    for y in range(h):
        d = 0.
        if x>0:
            d += setlink(x,y,x-1,y)
        if x<w-1:
            d += setlink(x,y,x+1,y)
        if y>0:
            d += setlink(x,y,x,y-1)
        if y<h-1:
            d += setlink(x,y,x,y+1)
        i = coords_to_index(x,y)
        A[i,i] = -d

A = A.tocsr()

# the greater this number, the more details it will pick up on. But it increases
# execution time, and after a while increasing it won't make much difference
n_eigs = 25

print "finding eigenvectors (this may take a while)"
L, V = spl.eigsh(A, k=n_eigs, tol=1e-12, which='LA')

print "found eigenvalues", L

out = Image.new("RGB", (w, h), "white")
out_pix = out.load()

print "painting picutre"

V = np.real(V)
n = np.size(V,0)
R = np.zeros(n)
G = np.zeros(n)
B = np.zeros(n)

for k in range(n_eigs-1):
    weight = 1./L[k]
    R = R + V[:,k]*np.random.randn()*weight
    G = G + V[:,k]*np.random.randn()*weight
    B = B + V[:,k]*np.random.randn()*weight

R -= np.min(R)
G -= np.min(G)
B -= np.min(B)
R /= np.max(R)
G /= np.max(G)
B /= np.max(B)

for x in range(w):
    for y in range(h):
        i = coords_to_index(x,y)
        r = R[i]
        g = G[i]
        b = B[i]
        pixval = tuple(int(v*256) for v in (r,g,b))
        out_pix[x,y] = pixval

out = out.resize((orig_w, orig_h), Image.ANTIALIAS)
out_pix = out.load()
orig_pix = orig_img.load()

for x in range(orig_w):
    for y in range(orig_h):
        r,g,b = out_pix[x,y]
        i = orig_pix[x,y]/255.
        out_pix[x,y] = tuple(int(v*i) for v in (r,g,b))

fname, extension = os.path.splitext(filename)
out.save('out_' + fname + '.png')

print("completed in %s seconds" % (time.time() - start_time))
Nathaniel
fonte
4
Isso é muito legal. Provavelmente um dos meus favoritos até agora. Você fez um excelente trabalho ao lidar com o antialiasing e as áreas abertas, e alguém finalmente coloriu o Link! ( Estava esperando por isso :-P salve o conjunto na área de trabalho ) Gostaria de saber o que meu antigo professor de inglês teria dito sobre isso como uma imagem estática ... "Mostra os dois lados do coração, de um lado há paz e outro, há a luta necessária para obter essa paz ". Chega de amor pelos jogos de Legend of Zelda ... É realmente uma pena que demore tanto. Qual o tamanho dos arquivos resultantes? Ps Love imagens 4 e 5
OliverGriffin
2
@donbright, um aluno da 3ª série que pudesse entender autovetores seria realmente um garoto muito inteligente - não sei se é possível explicar o algoritmo nesse nível. Mas deixe-me tentar de qualquer maneira: imagine que imprimimos a foto em uma folha rígida de metal. Depois, cortamos cuidadosamente todas as linhas pretas e as substituímos por algo muito mais flexível, como elástico. Portanto, as partes brancas são placas de metal e as partes pretas são tecidos flexíveis. Em seguida, penduramos a coisa toda no ar com barbante, para que fique livre para se mover. Agora se nós batemos as placas de metal, eles vão vibrar ...
Nathaniel
2
@donbright (continuação) ... Dependendo de como você bate na placa de metal, ela vibra de maneiras diferentes. Talvez algumas vezes apenas uma das peças de metal vibre e não as outras, mas outras vezes (porque elas são conectadas por elástico), bater em uma placa fará com que outra também se mova. Essas diferentes formas de vibração são chamadas modos vibracionais . Este programa simula alguns dos modos vibracionais dessa placa de metal, mas, em vez de gerar som, ele os utiliza para determinar qual cor desenhar.
Nathaniel
2
@donbright Você também pode ver aqui para mais informações sobre a visualização das vibrações de placas de metal.
Nathaniel
2
@donbright (essa explicação mais técnica também pode te perder um pouco, mas essa explicação funciona porque os modos vibracionais de uma placa também são calculados usando um cálculo de vetor próprio. Embora seja possível, não é exatamente o mesmo cálculo que meu código - eu sou realmente não tenho certeza).
Nathaniel
25

Python 2 + PIL também, meu primeiro livro de colorir

import sys, random
from PIL import Image

def is_whitish(color):
    return sum(color)>500

def get_zone(image, point, mask):
    pixels = image.load()
    w, h = image.size
    s = [point]
    while s:
        x, y = current = s.pop()
        mask[current] = 255
        yield current
        s+=[(i,j) for (i,j) in [(x,y-1),(x,y+1),(x-1,y),(x+1,y)] if 0<=i<w and 0<=j<h and mask[i,j]==0 and is_whitish(pixels[i,j])]

def get_zones(image):
    pixels = I.load()
    mask = Image.new('1',image.size).load()
    w,h = image.size
    for y in range(h):
        for x in range(w):
            p = x,y
            if mask[p]==0 and is_whitish(pixels[p]):
                yield get_zone(image, p, mask)



def apply_gradient(image, mincolor, maxcolor, points):
    minx = min([x for x,y in points])
    maxx = max([x for x,y in points])
    miny = min([y for x,y in points])
    maxy = max([y for x,y in points])
    if minx == maxx or miny==maxy:
        return
    diffx, diffy = (maxx - minx), (maxy-miny)
    stepr = (maxcolor[0] - mincolor[0] * 1.0) / diffy
    stepg = (maxcolor[1] - mincolor[1] * 1.0) / diffy
    stepb = (maxcolor[2] - mincolor[2] * 1.0) / diffy
    r,g,b = mincolor
    w, h = (abs(diffx+1),abs(diffy+1))
    tmp = Image.new('RGB', (w,h))
    tmppixels = tmp.load()
    for y in range(h):
        for x in range(w):
            tmppixels[x,y] = int(r), int(g), int(b)
        r+=stepr; g+=stepg; b+=stepb
    pixels = image.load()
    minx, miny = abs(minx), abs(miny)
    for x,y in points:
        try:
        pixels[x,y] = tmppixels[x-minx, y-miny]
    except Exception, e:
            pass

def colors_seq():
   yield (0,255,255)
   c = [(255,0,0),(0,255,0),(0,0,139)]
   i=0
   while True:i%=len(c);yield c[i];i+=1

def colorize(image):
    out = image.copy()
        COLORS = colors_seq()
    counter = 0
    for z in get_zones(image):
        c1 = COLORS.next()
        c2 = (0,0,0) if counter == 0 else (255,255,255)
        if counter % 2 == 1:
            c2, c1 = c1, c2
        apply_gradient(out, c1, c2, list(z))
        counter +=1
    return out

if __name__ == '__main__':
    I = Image.open(sys.argv[-1]).convert('RGB')
    colorize(I).show()

Fiz exatamente o mesmo que o CarpetPython, exceto pelo fato de preencher a região com 'gradientes' e usar um ciclo de cores diferente.

Meus corantes mais magníficos: insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

Tempos de computação na minha máquina:

  • image 1 (chinese dragon): real 0m2.862s usuário 0m2.801s sys 0m0.061s

  • imagem 2 (gryffon): usuário 0m0.991s real 0m0.963s sys 0m0.029s

  • imagem 3 (dragão unicorniano): real 0m2.260s usuário 0m2.239s sys 0m0.021s

dieter
fonte
Nice gradients! Quando você coloca um loop for dentro de um loop for com mais nada dentro do primeiro, você não precisa avançar mais?
OliverGriffin
com certeza ! Que foi copiar / colar questão ...
dieter
23

Python 2 e PIL: mundos psicodélicos

Eu usei um algoritmo simples para preencher cada área branca com uma cor de uma paleta de ciclismo. O resultado é muito colorido, mas não muito realista.

Observe que as partes "brancas" nessas fotos não são muito brancas. Você precisará testar também tons de cinza.

Código no Python 2.7:

import sys
from PIL import Image

WHITE = 200 * 3
cs = [60, 90, 120, 150, 180]
palette = [(199,199,199)] + [(R,G,B) for R in cs for G in cs for B in cs]

def fill(p, color):
    perim = {p}
    while perim:
        p = perim.pop()
        pix[p] = color
        x,y = p
        for u,v in [(x+dx, y+dy) for dx,dy in [(-1,0), (1,0), (0,1), (0,-1)]]:
            if 0 <= u < W and 0 <= v < H and sum(pix[(u,v)]) >= WHITE:
                perim.add((u,v))

for fname in sys.argv[1:]:
    print 'Processing', fname
    im = Image.open(fname)
    W,H = im.size
    pix = im.load()
    colornum = 0
    for y in range(H):
        for x in range(W):
            if sum(pix[(x,y)]) >= WHITE:
                thiscolor = palette[colornum % len(palette)]
                fill((x,y), thiscolor)
                colornum += 1
    im.save('out_' + fname)

Imagens de exemplo:

Um dragão colorido

Abóboras em LSD

Cavaleiro Lógico
fonte
3
A parte assustadora é que as cores realmente parecem funcionar. Quanto tempo você levou para colorir cada imagem e qual o tamanho dos arquivos?
OliverGriffin
11
O programa colore cada imagem em cerca de 2 segundos. As dimensões da imagem de saída são as mesmas dos arquivos de entrada. Os tamanhos dos arquivos são geralmente 10% a 40% menores que os originais (provavelmente porque são usadas configurações diferentes de compactação jpeg).
Logic Knight
3
Estou completamente impressionado com o quão curto é o código! Também gosto de como você limita efetivamente as cores disponíveis para uso, mantendo assim um palete definido. Na verdade, eu realmente gosto disso, isso meio que dá uma impressão de grunge (essa é a palavra certa? Eu não sou um artista).
OliverGriffin
@ Oliveriver, estou feliz que você gostou. Eu estava apontando para uma paleta sem cores brilhantes ou escuras, mas ainda tendo algum contraste. Essa faixa de cores parecia ter os resultados mais agradáveis.
Logic Knight
11

Matlab

function [output_image] = m3(input_file_name)
a=imread(input_file_name);
b=im2bw(a,0.85);
c=bwlabel(b);
h=vision.BlobAnalysis;
h.MaximumCount=10000;
ar=power(double(step(h,b)),0.15);
ar=[ar(1:max(max(c))),0];
f=cat(3,mod((ar(c+(c==0))-min(ar(1:end-1)))/ ...
    (max(ar(1:end-1))-min(ar(1:end-1)))*0.9+0.8,1),c*0+1,c*0+1);
g=hsv2rgb(f);
output_image=g.*cat(3,c~=0,c~=0,c~=0);

Usamos o espaço de cores HSV e escolhemos o Hue de cada região com base no tamanho relativo entre as regiões brancas. A maior região será azul ( Hue = 0.7) e a menor região será violeta ( Hue = 0.8). As regiões entre esses dois tamanhos recebem tons no intervalo 0.7 -> 1=0 -> 0.8. A Matiz no intervalo é selecionada linearmente em relação à função area^0.15. A saturação e o valor são sempre 1 para cada pixel não preto.

Demora menos de 1 segundo para colorir uma imagem.

As 3 imagens com regiões fechadas nas quais o algoritmo funciona decentemente:

Dragão

outro dragão

talvez outro dragão

E o resto das imagens:

Dragão

outro dragão

talvez outro dragão

Nessas imagens, existem grandes regiões conectadas em branco que devem ser idealmente coloridas por várias cores (esse problema foi bem resolvido na solução de Nathaniel .

randomra
fonte
Código agradável e curto para obter alguns resultados coordenados com cores bonitas! Gosto de como você usou a área para ajudar a determinar o tom. Quanto tempo levou para processar a imagem média e por que não funcionou em algumas das imagens mais detalhadas? As áreas eram muito pequenas?
OliverGriffin
11
@OliverGriffin Respondeu no meu post e adicionou o resto das imagens.
randomra
7

Python 3 com travesseiro

O código é um pouco longo para incluir nesta resposta, mas aqui está a essência .

  1. Pegue a imagem de entrada e, se ela tiver um canal alfa, componha-a em um fundo branco. (Necessário pelo menos para a imagem de galinhas, porque toda a imagem era preta, diferenciada apenas pela transparência, portanto, simplesmente largar o alfa não era útil.)
  2. Converta o resultado em escala de cinza; não queremos que artefatos de compactação ou suavização de serrilhado ou linhas cinzas que não são totalmente cinzas nos atrapalhem.
  3. Crie uma cópia em dois níveis (preto e branco) do resultado. Tons de cinza são convertidos em preto ou branco com base em um limite de corte configurável entre o branco e o tom mais escuro da imagem.
  4. Preencha todas as regiões brancas da imagem. As cores são escolhidas aleatoriamente, usando uma paleta selecionável que leva em consideração a localização do ponto de partida para a operação de preenchimento.
  5. Preencha as linhas pretas com as cores dos vizinhos mais próximos. Isso nos ajuda a reintroduzir o anti-aliasing, impedindo que todas as regiões coloridas sejam delimitadas em preto irregular.
  6. Pegue a imagem em escala de cinza da etapa 2 e faça dela uma máscara alfa: a cor mais escura é totalmente opaca, a cor mais clara é totalmente transparente.
  7. Componha a imagem em escala de cinza na imagem colorida da etapa 5 usando esta máscara alfa.

Infelizmente, esses últimos passos ainda não eliminaram "halos" mais claros que são visíveis em regiões de cores mais escuras, mas fizeram uma diferença notável, pelo menos. O processamento de imagens nunca foi meu campo de estudo, então, pelo que sei, existem algoritmos mais bem-sucedidos e mais eficientes para fazer o que tentei fazer aqui ... mas tudo bem.

Até agora, existem apenas duas paletas selecionáveis ​​para a etapa 4: uma puramente aleatória e uma muito natural "natural", que tenta atribuir cores do céu aos cantos superiores, cores da grama aos cantos inferiores, marrons (pedras ou madeira ) cores no meio de cada lado e cores variadas no centro. O sucesso foi ... limitado.


Uso:

usage: paint_by_prog.py [-h] [-p PALETTE] [-t THRESHOLD] [-f | -F] [-d]
                        FILE [FILE ...]

Paint one or more line-art images.

positional arguments:
  FILE                  one or more image filenames

optional arguments:
  -h, --help            show this help message and exit
  -p PALETTE, --palette PALETTE
                        a palette from which to choose colours; one of
                        "random" (the default) or "natural"
  -t THRESHOLD, --threshold THRESHOLD
                        the lightness threshold between outlines and paintable
                        areas (a proportion from 0 to 1)
  -f, --proper-fill     fill under black lines with proper nearest-neighbour
                        searching (slow)
  -F, ---no-proper-fill
                        fill under black lines with approximate nearest-
                        neighbour searching (fast)
  -d, --debug           output debugging information

Amostras:

paint_by_prog.py -t 0.7 Gryphon-Lines.png Grifo colorido

paint_by_prog.py Dragon-Tattoo-Outline.jpg Dragão cartoony colorido

paint_by_prog.py -t 0.85 -p natural The-Pumpkin-Farm-of-Good-old-Days.jpg Cena de fazenda colorida

paint_by_prog.py -t 0.7 Dragon-OutLine.jpg Dragão colorido grunge

paint_by_prog.py stejar-arbore-schiţă-natura.png Árvore colorida, parecendo muito parecida com uma bandeira

O frango não parece muito bom, e meu resultado mais recente para a imagem do Link não é o melhor; aquele que veio de uma versão anterior do código era basicamente amarelo pálido e tinha uma vibração interessante no deserto ...


Atuação:

Cada imagem leva alguns segundos para processar com as configurações padrão, o que significa que um algoritmo aproximado do vizinho mais próximo é usado para a etapa 5. O vizinho mais próximo verdadeiro é significativamente mais lento, levando talvez meio minuto (eu realmente não o cronometrei).

Tim Pederick
fonte
A primeira imagem parece fantástica, especialmente aquele olho castanho. Bom trabalho. Também o aplaudo por ter capim verde, campos marrons de abóboras e nuvens roxas.
OliverGriffin
3

Java

Seleção aleatória de cores de sua escolha de paleta.

Aviso: a região está atualmente muito lenta, a menos que as regiões brancas sejam incomumente pequenas.

import java.awt.Color;
import java.awt.image.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.function.Supplier;

import javax.imageio.ImageIO;


public class Colorer{
    public static boolean isProbablyWhite(int x,int y){
        Color c=new Color(image.getRGB(x, y));
        if(c.getRed()<240)return false;
        if(c.getBlue()<240)return false;
        if(c.getGreen()<240)return false;
        return true;
    }
    static class Point{
        int x,y;
        public boolean equals(Object o){
            if(o instanceof Point){
                Point p=(Point)o;
                return x==p.x&&y==p.y;
            }
            return false;
        }
        public Point(int x,int y){
            this.x=x;
            this.y=y;
        }
    }
    static BufferedImage image;
    static int W,H;
    public static void check(Point p,List<Point>l1,List<Point>l2,List<Point>l3){
        if(!isProbablyWhite(p.x,p.y))return;
        if(l1.contains(p))return;
        if(l2.contains(p))return;
        if(l3.contains(p))return;
        l1.add(p);
    }
    public static void process(int x,int y,Color c){
        List<Point>plist=new LinkedList<>();
        int rgb=c.getRGB();
        plist.add(new Point(x,y));
        List<Point>l3=new LinkedList<>();
        int k=0;
        for(int i=0;i<W*H;i++){
            System.out.println(k=l3.size());
            List<Point>l2=new LinkedList<>();
            for(Point p:plist){
                int x1=p.x;
                int y1=p.y;
                if(x1>0){
                    check(new Point(x1-1,y1),l2,plist,l3);
                }
                if(y1>0){
                    check(new Point(x1,y1-1),l2,plist,l3);
                }
                if(x1<W-1){
                    check(new Point(x1+1,y1),l2,plist,l3);
                }
                if(y1<H-1){
                    check(new Point(x1,y1+1),l2,plist,l3);
                }
            }
            while(!plist.isEmpty()){
                l3.add(plist.remove(0));
            }
            if(l3.size()==k)break;
            plist=l2;
        }
        plist=l3;
        for(Point p:plist){
            image.setRGB(p.x,p.y,rgb);
        }
    }
    public static void main(String[]args) throws Exception{
        Random rand=new Random();
        List<Supplier<Color>>colgen=new ArrayList<>();
        colgen.add(()->{return new Color(rand.nextInt(20),50+rand.nextInt(200),70+rand.nextInt(180));});
        colgen.add(()->{return new Color(rand.nextInt(20),rand.nextInt(40),70+rand.nextInt(180));});
        colgen.add(()->{return new Color(150+rand.nextInt(90),10+rand.nextInt(120),rand.nextInt(5));});
        colgen.add(()->{int r=rand.nextInt(200);return new Color(r,r,r);});
        colgen.add(()->{return Arrays.asList(new Color(255,0,0),new Color(0,255,0),new Color(0,0,255)).get(rand.nextInt(3));});
        colgen.add(()->{return Arrays.asList(new Color(156,189,15),new Color(140,173,15),new Color(48,98,48),new Color(15,56,15)).get(rand.nextInt(4));});
        Scanner in=new Scanner(System.in);
        image=ImageIO.read(new File(in.nextLine()));
        final Supplier<Color>sup=colgen.get(in.nextInt());
        W=image.getWidth();
        H=image.getHeight();
        for(int x=0;x<W;x++){
            for(int y=0;y<H;y++){
                if(isProbablyWhite(x,y))process(x,y,sup.get());
            }
        }
        ImageIO.write(image,"png",new File("out.png"));
    }
}

Requer duas entradas: o nome do arquivo e o ID da paleta. Inclui alguma correção de suavização de borda, mas não inclui lógica para pixels transparentes.

As seguintes paletas são atualmente reconhecidas:

0: Blue and greeen
1: Blue
2: Red
3: Greyscale
4: Three-color Red, Green, and Blue
5: Classic Game Boy pallette (four shades of green)

Resultados:

Dragão, paleta Game Boy:

insira a descrição da imagem aqui

O outro dragão, paleta azul + verde:

insira a descrição da imagem aqui

GOL still life mona lisa (conforme processada por este programa ), paleta tricolor:

insira a descrição da imagem aqui

SuperJedi224
fonte
+1 para personalizar a cor! :) se você pudesse corrigir o problema de antialiasing, isso ficaria ainda melhor. Quanto tempo você levou para produzir essas imagens?
OliverGriffin