Python 3.4
- Bonus 1: Self Inverse: Wiederholen stellt das Originalbild wieder her.
- Optionales Schlüsselbild: Das Originalbild kann nur wiederhergestellt werden, wenn dasselbe Schlüsselbild erneut verwendet wird.
- Bonus 2: Muster in der Ausgabe erzeugen: Das Schlüsselbild wird in den verwürfelten Pixeln angenähert.
Bei Erreichen von Bonus 2 durch Verwendung eines zusätzlichen Schlüsselbildes geht Bonus 1 nicht verloren. Das Programm ist immer noch selbstinvers, sofern es erneut mit demselben Schlüsselbild ausgeführt wird.
Standardgebrauch
Testbild 1:
Testbild 2:
Wenn Sie das Programm mit einer einzelnen Bilddatei als Argument ausführen, wird eine Bilddatei gespeichert, deren Pixel gleichmäßig über das gesamte Bild verteilt sind. Wenn Sie es mit der verschlüsselten Ausgabe erneut ausführen, wird eine Bilddatei mit der erneut angewendeten Verschlüsselung gespeichert, wodurch das Original wiederhergestellt wird, da der Verschlüsselungsprozess seine eigene Umkehrung darstellt.
Der Scrambling-Prozess ist selbstinvers, da die Liste aller Pixel in 2 Zyklen aufgeteilt ist, so dass jedes Pixel mit einem und nur einem anderen Pixel ausgetauscht wird. Wenn Sie es ein zweites Mal ausführen, wird jedes Pixel mit dem Pixel ausgetauscht, mit dem es zuerst ausgetauscht wurde, und alles wird auf den ursprünglichen Zustand zurückgesetzt. Wenn es eine ungerade Anzahl von Pixeln gibt, wird es eines geben, das sich nicht bewegt.
Dank der Antwort von mfvonh als erstem, der 2 Zyklen vorschlägt.
Verwendung mit einem Schlüsselbild
Scrambling Testbild 1 mit Testbild 2 als Schlüsselbild
Scrambling Testbild 2 mit Testbild 1 als Schlüsselbild
Wenn Sie das Programm mit einem zweiten Argument für eine Bilddatei (dem Schlüsselbild) ausführen, wird das Originalbild basierend auf dem Schlüsselbild in Bereiche unterteilt. Jede dieser Regionen ist separat in 2 Zyklen unterteilt, so dass das gesamte Verwürfeln innerhalb von Regionen auftritt und Pixel nicht von einer Region zu einer anderen verschoben werden. Dadurch werden die Pixel über jede Region verteilt, und die Regionen erhalten eine einheitliche gesprenkelte Farbe, jedoch mit einer geringfügig unterschiedlichen Durchschnittsfarbe für jede Region. Dies gibt eine sehr grobe Annäherung an das Schlüsselbild in den falschen Farben.
Bei erneuter Ausführung werden dieselben Pixelpaare in jeder Region ausgetauscht, sodass jede Region in ihren ursprünglichen Zustand zurückgesetzt wird und das Bild als Ganzes wieder angezeigt wird.
Dank der Antwort von edc65 als erstem Vorschlag, das Bild in Regionen zu unterteilen. Ich wollte dies erweitern, um beliebige Regionen zu verwenden, aber der Ansatz, alles in Region 1 mit allem in Region 2 zu tauschen, bedeutete, dass die Regionen von identischer Größe sein mussten. Meine Lösung besteht darin, die Regionen voneinander isoliert zu halten und einfach jede Region in sich selbst zu mischen. Da Regionen nicht länger von ähnlicher Größe sein müssen, wird es einfacher, beliebig geformte Regionen anzuwenden.
Code
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')
Brennen von JPEG-Bildern
.jpg-Dateien werden sehr schnell verarbeitet, jedoch auf Kosten der Überhitzung. Dies hinterlässt ein eingebranntes Nachbild, wenn das Original wiederhergestellt wird:
Im Ernst, ein verlustbehaftetes Format führt dazu, dass einige der Pixelfarben geringfügig geändert werden, wodurch die Ausgabe an sich ungültig wird. Wenn ein Schlüsselbild verwendet wird und das Mischen von Pixeln auf Regionen beschränkt ist, wird die gesamte Verzerrung in der Region beibehalten, in der sie aufgetreten ist, und verteilt sich dann gleichmäßig über diese Region, wenn das Bild wiederhergestellt wird. Der Unterschied in der durchschnittlichen Verzerrung zwischen Regionen lässt einen sichtbaren Unterschied zwischen ihnen übrig, so dass die Regionen, die beim Verwürfelungsprozess verwendet werden, im wiederhergestellten Bild immer noch sichtbar sind.
Durch die Konvertierung in das PNG-Format (oder in ein verlustfreies Format) vor dem Verschlüsseln wird sichergestellt, dass das unverschlüsselte Bild mit dem Original identisch ist und weder Brennen noch Verzerren auftritt:
Kleine Details
- Regionen sind mindestens 256 Pixel groß. Wenn das Bild in zu kleine Bereiche aufgeteilt werden könnte, wäre das Originalbild nach dem Verwürfeln teilweise noch sichtbar.
- Wenn es mehr als eine Region mit einer ungeraden Anzahl von Pixeln gibt, wird ein Pixel aus der zweiten Region der ersten zugewiesen, und so weiter. Dies bedeutet, dass es immer nur einen Bereich mit einer ungeraden Anzahl von Pixeln geben kann, sodass nur ein Pixel unverschlüsselt bleibt.
- Es gibt ein drittes optionales Argument, das die Anzahl der Regionen einschränkt. Wenn Sie dies beispielsweise auf 2 setzen, erhalten Sie mit zwei Tönen verwürfelte Bilder. Dies kann je nach den beteiligten Bildern besser oder schlechter aussehen. Wenn hier eine Nummer angegeben wird, kann das Image nur mit derselben Nummer wiederhergestellt werden.
- Die Anzahl der unterschiedlichen Farben im Originalbild begrenzt auch die Anzahl der Regionen. Wenn das Originalbild zweifarbig ist, können ungeachtet des Schlüsselbilds oder des dritten Arguments nur maximal 2 Regionen vorhanden sein.