Python 3.4
- Bonus 1: Auto inverso: la ripetizione ripristina l'immagine originale.
- Immagine chiave opzionale: l'immagine originale può essere ripristinata solo usando nuovamente la stessa immagine chiave.
- Bonus 2: produzione di pattern nell'output: l'immagine chiave è approssimata nei pixel criptati.
Quando si ottiene il bonus 2, utilizzando un'immagine chiave aggiuntiva, il bonus 1 non viene perso. Il programma è ancora auto-inverso, purché venga nuovamente eseguito con la stessa immagine chiave.
Utilizzo standard
Immagine di prova 1:
Immagine di prova 2:
L'esecuzione del programma con un singolo file di immagine come argomento salva un file di immagine con i pixel mescolati uniformemente sull'intera immagine. L'esecuzione di nuovo con l'output criptato salva un file di immagine con il rimescolamento applicato di nuovo, il che ripristina l'originale poiché il processo di criptaggio è il suo contrario.
Il processo di scrambling è auto-inverso perché l'elenco di tutti i pixel è diviso in 2 cicli, in modo che ogni pixel sia scambiato con un solo e un altro pixel. Eseguendolo una seconda volta scambia ogni pixel con il pixel con cui è stato scambiato per la prima volta, riportando tutto a come è iniziato. Se c'è un numero dispari di pixel ce ne sarà uno che non si sposta.
Grazie alla risposta di mfvonh come primo a suggerire 2 cicli.
Utilizzo con un'immagine chiave
Scrambling Immagine di prova 1 con Immagine di prova 2 come immagine chiave
Scrambling Test immagine 2 con Test immagine 1 come immagine chiave
L'esecuzione del programma con un secondo argomento del file di immagine (l'immagine chiave) divide l'immagine originale in regioni in base all'immagine chiave. Ognuna di queste regioni è divisa in 2 cicli separatamente, in modo che tutto il rimescolamento avvenga all'interno delle regioni e i pixel non vengano spostati da una regione all'altra. Questo distribuisce i pixel su ogni regione e quindi le regioni diventano un colore maculato uniforme, ma con un colore medio leggermente diverso per ogni regione. Ciò fornisce un'approssimazione molto approssimativa dell'immagine chiave, con colori sbagliati.
La nuova esecuzione scambia le stesse coppie di pixel in ciascuna regione, quindi ciascuna regione viene ripristinata al suo stato originale e riappare l'immagine nel suo insieme.
Grazie alla risposta di edc65 come il primo a suggerire di dividere l'immagine in regioni. Volevo espandermi su questo per usare regioni arbitrarie, ma l'approccio di scambiare tutto nella regione 1 con tutto nella regione 2 significava che le regioni dovevano avere le stesse dimensioni. La mia soluzione è quella di mantenere le regioni isolate l'una dall'altra e semplicemente mescolare ciascuna regione in se stessa. Poiché le regioni non devono più avere dimensioni simili, diventa più semplice applicare regioni di forma arbitraria.
Codice
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')
Masterizzazione di immagini JPEG
I file .jpg vengono elaborati molto rapidamente, ma a scapito di essere troppo caldi. Questo lascia un'immagine masterizzata dopo il ripristino dell'originale:
Ma seriamente, un formato con perdita comporterà una leggera modifica di alcuni dei colori dei pixel, il che di per sé rende l'output non valido. Quando viene utilizzata un'immagine chiave e il mescolamento dei pixel è limitato alle regioni, tutta la distorsione viene mantenuta all'interno della regione in cui si è verificata e quindi distribuita uniformemente su quella regione quando l'immagine viene ripristinata. La differenza nella distorsione media tra le regioni lascia una differenza visibile tra loro, quindi le regioni utilizzate nel processo di rimescolamento sono ancora visibili nell'immagine ripristinata.
La conversione in .png (o in qualsiasi formato senza perdita di dati) prima della confusione assicura che l'immagine non decodificata sia identica all'originale senza bruciature o distorsioni:
Piccoli dettagli
- Una dimensione minima di 256 pixel è imposta alle regioni. Se all'immagine fosse permesso di dividere in aree troppo piccole, l'immagine originale sarebbe comunque parzialmente visibile dopo la confusione.
- Se esiste più di una regione con un numero dispari di pixel, un pixel della seconda regione viene riassegnato alla prima e così via. Ciò significa che può esistere solo una regione con un numero dispari di pixel, quindi solo un pixel rimarrà decodificato.
- C'è un terzo argomento facoltativo che limita il numero di regioni. Impostando questo su 2 per esempio, si otterranno immagini confuse a due toni. Questo può apparire migliore o peggiore a seconda delle immagini coinvolte. Se qui viene specificato un numero, l'immagine può essere ripristinata usando nuovamente lo stesso numero.
- Il numero di colori distinti nell'immagine originale limita anche il numero di regioni. Se l'immagine originale ha due tonalità, indipendentemente dall'immagine chiave o dal terzo argomento, possono esserci solo un massimo di 2 regioni.