Determinando o contraste da imagem

Cenário

Imagine o seguinte cenário de terror . Você está criando um CMS no Rails e, para um determinado tipo de página, deseja ter uma área de cabeçalho com uma imagem carregada pelo usuário como plano de fundo e algum texto por cima dessa imagem. Você não tem ideia se o usuário irá carregar uma imagem totalmente branca, uma imagem totalmente preta ou algo intermediário. Então, de que cor você faz o texto …

Se você for inteligente, poderá determinar o contraste da imagem e, em seguida, exibir o texto sobreposto em uma cor que se adapte à imagem por trás dele. Para começar, presume-se que você cuidou de sua imagem com algo como Paperclip , que recomendo vivamente. Seja qual for a sua escolha, você precisará do ImageMagick no servidor. Você também precisará ter um formulário que permita ao usuário adicionar sua imagem e texto, pois é a atualização deste recurso que vai desencadear a mágica. Então, digamos que este recurso seja chamado de Capa e dentro da capa você tenha uma imagem e um atributo de sobreposição , você também precisará adicionar um atributo escuro booleano a isso. Então seuO modelo de capa deve ser assim:

class Cover < ActiveRecord::Base
attr_accessible
:dark, :overlay, :image
has_attached_file
:image, :styles => {:blur => "1000x600" }, :convert_options => { :blur => "-blur 0x8" } #this line is specific to Paperclip
end

O tem anexado arquivo é Paperclip específico, por isso, se você estiver usando uma outra coisa para cuidar de sua imagem não se preocupe. O que precisamos fazer é obter um retorno de chamada ao atualizar ou salvar como usei:

after_save :determine_image_contrast

Em seguida, a função precisará calcular o contraste e definir o valor de escuro como verdadeiro ou falso, dependendo da imagem. Felizmente, ImageMagick é seu amigo e, embora um pouco hacky, o seguinte fará isso:

def determine_image_contrast
output
= %x[convert #{Dir.pwd}/public#{image.url(:original, timestamp: false)} -colorspace gray -resize 1x1 txt:- 2>&1]
terms
= /\#([a-zA-Z]|[0-9]){3,6}/.match(output).to_s.split(/(...)(..)(..)/)
self.update_column(:dark, (terms[2].hex.to_i() < 130))
return true
end

Dividindo isso, você pode ver que a primeira linha está executando uma função de linha de comando convert, que é a parte principal do que o ImageMagick faz, convertendo imagens em algo. O primeiro parâmetro é o local da imagem # {Dir.pwd} / public # {image.url (: original, timestamp: false)} que é o diretório público do diretório em execução e a imagem relevante dentro dele. Novamente, a parte image.url é específica do Paperclip e é importante remover o timestamp do Paperclip, pois isso bagunça o comando. As próximas partes -colorspace grey -resize 1×1 txt: – está dizendo ao ImageMagick para renderizar a imagem inteira em um quadrado de 1×1, escalonar esse quadrado em tons de cinza e gerar o resultado como texto, que se você o visse lhe daria algo como:

# ImageMagick pixel enumeration: 1,1,255,gray
0,0: (145,145,145) #919191 gray(145,145,145)

Os termos da próxima linha = / # ([a-zA-Z] | [0-9]) {3,6} /. Match (output) .to_s.split (/ (…) (..) (. .) /) extrai o valor #hex que representa o tom de cinza e o divide em seus valores R, G, B de componentes (que são todos iguais). Em seguida, ele atualiza o valor escuro com base em se o valor G é maior ou menor que 130, que é um limite que você pode ajustar facilmente. A parte importante aqui é usar o self.update_column, qualquer outra coisa acionará um save, que por sua vez acionará essa função e você terminará com um loop infinito! Por fim, a função retorna true, simplesmente porque li que algumas outras funções na linha podem exigir isso, mas ainda não descobri o porquê! Depois de ter os dados entrando enquanto a imagem é carregada, você deve testar isso primeiro, você pode então começar a brincar com as coisas do lado do cliente, primeiro adicionando alguns CSS como:

.jumbo-light{
color
: black;
text
-shadow: -1px 0px 1px #FFF, 1px 0px 1px #FFF, 0px 1px 1px #FFF, 0px -1px 1px #FFF;
}
.jumbo-dark{
color
: white;
text
-shadow: -1px 0px 1px #000, 1px 0px 1px #000, 0px 1px 1px #000, 0px -1px 1px #000;
}

Em seguida, adicione o seguinte aos seus layouts que usam este recurso específico:

.jumbotron{:style => "background-image:url(#{@cover.image.url(:blur)});"}
%h1{:class => "#{@cover.dark ? 'jumbo-dark' : 'jumbo-light'}"} Hello nurse!

O que até agora está fazendo um trabalho realmente impressionante para nós. Você poderia facilmente estender isso não dimensionando a imagem para cinza e realmente analisando cada um dos canais RGB, para que pudesse exibir azul nas imagens com mais vermelho, amarelo em verde etc.