Python 3.4
- Bônus 1: Inverso: repetir restaura a imagem original.
- Imagem principal opcional: a imagem original só pode ser restaurada usando a mesma imagem principal novamente.
- Bônus 2: Padrão de produção na saída: a imagem principal é aproximada nos pixels embaralhados.
Quando o bônus 2 é alcançado, usando uma imagem-chave adicional, o bônus 1 não é perdido. O programa ainda é auto-inverso, desde que seja executado com a mesma imagem-chave novamente.
Uso padrão
Imagem de teste 1:
Imagem de teste 2:
A execução do programa com um único arquivo de imagem como argumento salva um arquivo de imagem com os pixels misturados uniformemente por toda a imagem. A execução novamente com a saída codificada salva um arquivo de imagem com a codificação aplicada novamente, o que restaura o original, pois o processo de codificação é inverso.
O processo de codificação é inverso, porque a lista de todos os pixels é dividida em 2 ciclos, para que cada pixel seja trocado por um e apenas um outro pixel. A execução uma segunda vez troca todos os pixels com os pixels com os quais foi trocada pela primeira vez, colocando tudo de volta ao início. Se houver um número ímpar de pixels, haverá um que não se moverá.
Graças à resposta de mfvonh como a primeira a sugerir 2 ciclos.
Uso com uma imagem principal
Imagem de teste de embaralhamento 1 com imagem de teste 2 como imagem principal
Imagem de teste de codificação 2 com imagem de teste 1 como imagem principal
A execução do programa com um segundo argumento de arquivo de imagem (a imagem principal) divide a imagem original em regiões com base na imagem principal. Cada uma dessas regiões é dividida em 2 ciclos separadamente, para que toda a confusão ocorra dentro das regiões e os pixels não sejam movidos de uma região para outra. Isso espalha os pixels sobre cada região e, portanto, as regiões se tornam uma cor manchada uniforme, mas com uma cor média ligeiramente diferente para cada região. Isso fornece uma aproximação aproximada da imagem principal, nas cores erradas.
A execução novamente troca os mesmos pares de pixels em cada região, para que cada região seja restaurada ao seu estado original e a imagem como um todo reapareça.
Graças à resposta do edc65 como a primeira a sugerir a divisão da imagem em regiões. Eu queria expandir isso para usar regiões arbitrárias, mas a abordagem de trocar tudo na região 1 por tudo na região 2 significava que as regiões tinham que ter o mesmo tamanho. Minha solução é manter as regiões isoladas umas das outras e simplesmente embaralhar cada região em si mesma. Como as regiões não precisam mais ter tamanho semelhante, torna-se mais simples aplicar regiões de formato arbitrário.
Código
import os.path
from PIL import Image # Uses Pillow, a fork of PIL for Python 3
from random import randrange, seed
def scramble(input_image_filename, key_image_filename=None,
number_of_regions=16777216):
input_image_path = os.path.abspath(input_image_filename)
input_image = Image.open(input_image_path)
if input_image.size == (1, 1):
raise ValueError("input image must contain more than 1 pixel")
number_of_regions = min(int(number_of_regions),
number_of_colours(input_image))
if key_image_filename:
key_image_path = os.path.abspath(key_image_filename)
key_image = Image.open(key_image_path)
else:
key_image = None
number_of_regions = 1
region_lists = create_region_lists(input_image, key_image,
number_of_regions)
seed(0)
shuffle(region_lists)
output_image = swap_pixels(input_image, region_lists)
save_output_image(output_image, input_image_path)
def number_of_colours(image):
return len(set(list(image.getdata())))
def create_region_lists(input_image, key_image, number_of_regions):
template = create_template(input_image, key_image, number_of_regions)
number_of_regions_created = len(set(template))
region_lists = [[] for i in range(number_of_regions_created)]
for i in range(len(template)):
region = template[i]
region_lists[region].append(i)
odd_region_lists = [region_list for region_list in region_lists
if len(region_list) % 2]
for i in range(len(odd_region_lists) - 1):
odd_region_lists[i].append(odd_region_lists[i + 1].pop())
return region_lists
def create_template(input_image, key_image, number_of_regions):
if number_of_regions == 1:
width, height = input_image.size
return [0] * (width * height)
else:
resized_key_image = key_image.resize(input_image.size, Image.NEAREST)
pixels = list(resized_key_image.getdata())
pixel_measures = [measure(pixel) for pixel in pixels]
distinct_values = list(set(pixel_measures))
number_of_distinct_values = len(distinct_values)
number_of_regions_created = min(number_of_regions,
number_of_distinct_values)
sorted_distinct_values = sorted(distinct_values)
while True:
values_per_region = (number_of_distinct_values /
number_of_regions_created)
value_to_region = {sorted_distinct_values[i]:
int(i // values_per_region)
for i in range(len(sorted_distinct_values))}
pixel_regions = [value_to_region[pixel_measure]
for pixel_measure in pixel_measures]
if no_small_pixel_regions(pixel_regions,
number_of_regions_created):
break
else:
number_of_regions_created //= 2
return pixel_regions
def no_small_pixel_regions(pixel_regions, number_of_regions_created):
counts = [0 for i in range(number_of_regions_created)]
for value in pixel_regions:
counts[value] += 1
if all(counts[i] >= 256 for i in range(number_of_regions_created)):
return True
def shuffle(region_lists):
for region_list in region_lists:
length = len(region_list)
for i in range(length):
j = randrange(length)
region_list[i], region_list[j] = region_list[j], region_list[i]
def measure(pixel):
'''Return a single value roughly measuring the brightness.
Not intended as an accurate measure, simply uses primes to prevent two
different colours from having the same measure, so that an image with
different colours of similar brightness will still be divided into
regions.
'''
if type(pixel) is int:
return pixel
else:
r, g, b = pixel[:3]
return r * 2999 + g * 5869 + b * 1151
def swap_pixels(input_image, region_lists):
pixels = list(input_image.getdata())
for region in region_lists:
for i in range(0, len(region) - 1, 2):
pixels[region[i]], pixels[region[i+1]] = (pixels[region[i+1]],
pixels[region[i]])
scrambled_image = Image.new(input_image.mode, input_image.size)
scrambled_image.putdata(pixels)
return scrambled_image
def save_output_image(output_image, full_path):
head, tail = os.path.split(full_path)
if tail[:10] == 'scrambled_':
augmented_tail = 'rescued_' + tail[10:]
else:
augmented_tail = 'scrambled_' + tail
save_filename = os.path.join(head, augmented_tail)
output_image.save(save_filename)
if __name__ == '__main__':
import sys
arguments = sys.argv[1:]
if arguments:
scramble(*arguments[:3])
else:
print('\n'
'Arguments:\n'
' input image (required)\n'
' key image (optional, default None)\n'
' number of regions '
'(optional maximum - will be as high as practical otherwise)\n')
Gravação de imagem JPEG
Os arquivos .jpg são processados muito rapidamente, mas com o custo de ficar muito quente. Isso deixa uma imagem queimada depois que o original é restaurado:
Mas, falando sério, um formato com perdas resultará na alteração leve de algumas cores dos pixels, o que por si só torna a saída inválida. Quando uma imagem principal é usada e o embaralhamento de pixels é restrito às regiões, toda a distorção é mantida na região em que ocorreu e depois se espalha uniformemente nessa região quando a imagem é restaurada. A diferença na distorção média entre as regiões deixa uma diferença visível entre elas; portanto, as regiões usadas no processo de embaralhamento ainda são visíveis na imagem restaurada.
A conversão para .png (ou qualquer formato sem perdas) antes da codificação garante que a imagem não codificada seja idêntica à original sem gravação ou distorção:
Pequenos detalhes
- Um tamanho mínimo de 256 pixels é imposto às regiões. Se a imagem fosse dividida em regiões muito pequenas, a imagem original ainda estaria parcialmente visível após a codificação.
- Se houver mais de uma região com um número ímpar de pixels, um pixel da segunda região será reatribuído para o primeiro e assim por diante. Isso significa que só pode haver uma região com um número ímpar de pixels e, portanto, apenas um pixel permanecerá sem codificação.
- Há um terceiro argumento opcional que restringe o número de regiões. Ajustar para 2, por exemplo, fornecerá imagens embaralhadas em dois tons. Isso pode parecer melhor ou pior, dependendo das imagens envolvidas. Se um número for especificado aqui, a imagem poderá ser restaurada apenas usando o mesmo número novamente.
- O número de cores distintas na imagem original também limita o número de regiões. Se a imagem original for de dois tons, independentemente da imagem principal ou do terceiro argumento, poderá haver no máximo 2 regiões.