mirror of
https://codeberg.org/vlw/collage.git
synced 2025-09-13 15:23:40 +02:00
Release 1.0.0
Initial release
This commit is contained in:
parent
090f68cd0e
commit
654aa1ef32
8 changed files with 190 additions and 50 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,8 @@
|
|||
__pycache__
|
||||
__sample__.json
|
||||
|
||||
input/*
|
||||
samples/*
|
||||
mem/*
|
||||
memory/*
|
||||
|
||||
!*/.placeholder
|
74
classes/Collage.py
Normal file
74
classes/Collage.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from PIL import Image
|
||||
from bisect import bisect_left
|
||||
|
||||
class Schematic():
|
||||
def __init__(self,template,samples):
|
||||
self.template = template
|
||||
self.samples = samples
|
||||
|
||||
self.schematic = {}
|
||||
self.create_schematic()
|
||||
|
||||
def query_sample(self,value):
|
||||
samples = [*self.samples]
|
||||
pos = bisect_left(samples,value)
|
||||
|
||||
if(pos < 1 or pos > len(samples) - 1):
|
||||
pos = 0
|
||||
|
||||
return samples[pos]
|
||||
|
||||
def create_schematic(self):
|
||||
for x in range(1,self.template.size[0]):
|
||||
self.schematic[x] = {}
|
||||
for y in range(1,self.template.size[1]):
|
||||
r,g,b = self.template.getpixel((x,y))
|
||||
eyedropper = "%02x%02x%02x" % (r,g,b)
|
||||
|
||||
self.schematic[x][y] = self.query_sample(eyedropper)
|
||||
print(f"Generated schematic for pixel at index [{x},{y}] ",end="\r",flush="True")
|
||||
print("")
|
||||
|
||||
class Collage():
|
||||
def __init__(self,input_file,samples):
|
||||
self.template = Image.open(input_file)
|
||||
self.samples = samples.samples
|
||||
self.samples_posix = samples.samples_posix
|
||||
|
||||
self.size = (20,20)
|
||||
|
||||
self.collage = self.create_canvas()
|
||||
self.create_collage()
|
||||
|
||||
def create_canvas(self):
|
||||
canvas_width = self.size[0] * self.template.size[0]
|
||||
canvas_height = self.size[1] * self.template.size[1]
|
||||
|
||||
return Image.new("RGB",(canvas_width,canvas_height))
|
||||
|
||||
def create_collage(self):
|
||||
schematic = Schematic(self.template,self.samples).schematic
|
||||
|
||||
offset_x = 0
|
||||
offset_y = 0
|
||||
|
||||
for x in range(1,self.template.size[0]):
|
||||
offset_x = 0
|
||||
for y in range(1,self.template.size[1]):
|
||||
key = schematic[x][y]
|
||||
resolve_posix = self.samples[key]
|
||||
|
||||
sample = Image.open(self.samples_posix[resolve_posix])
|
||||
sample = sample.resize(self.size)
|
||||
|
||||
self.collage = self.collage.copy()
|
||||
self.collage.paste(sample,(offset_x,offset_y))
|
||||
|
||||
offset_x += self.size[0]
|
||||
|
||||
print(f"Pasted best matched sample for index [{x},{y}] ",end="\r",flush="True")
|
||||
offset_y += self.size[1]
|
||||
print("")
|
||||
|
||||
def put(self,dest):
|
||||
self.collage.save(dest,"JPEG")
|
31
classes/Color.py
Normal file
31
classes/Color.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
from PIL import Image
|
||||
|
||||
# Calculate the average color of a sample
|
||||
class AverageColor():
|
||||
def __init__(self,image):
|
||||
self.image = Image.open(image)
|
||||
self.image = self.image.resize((50,50)) # Downscale image to improve performance
|
||||
|
||||
def rgb(self):
|
||||
width,height = self.image.size
|
||||
rgb = [0,0,0]
|
||||
|
||||
def average(value):
|
||||
return round(value / lines)
|
||||
|
||||
# Get RGB of each pixel with by raster scanning
|
||||
lines = 0
|
||||
for x in range(0,width):
|
||||
for y in range(0,height):
|
||||
r,g,b = self.image.getpixel((x,y))
|
||||
|
||||
rgb[0] += r
|
||||
rgb[1] += g
|
||||
rgb[2] += b
|
||||
lines += 1
|
||||
|
||||
return tuple(map(average,rgb))
|
||||
|
||||
# Format RGB output as HEX (without #)
|
||||
def hex(self):
|
||||
return "%02x%02x%02x" % self.rgb()
|
|
@ -1,24 +0,0 @@
|
|||
from PIL import Image
|
||||
|
||||
def compute_average_image_color(img):
|
||||
width, height = img.size
|
||||
|
||||
r_total = 0
|
||||
g_total = 0
|
||||
b_total = 0
|
||||
|
||||
count = 0
|
||||
for x in range(0, width):
|
||||
for y in range(0, height):
|
||||
r, g, b = img.getpixel((x,y))
|
||||
r_total += r
|
||||
g_total += g
|
||||
b_total += b
|
||||
count += 1
|
||||
|
||||
return (r_total/count, g_total/count, b_total/count)
|
||||
|
||||
img = Image.open('image.png')
|
||||
#img = img.resize((50,50)) # Small optimization
|
||||
average_color = compute_average_image_color(img)
|
||||
print(average_color)
|
|
@ -1,26 +1,73 @@
|
|||
import zlib
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from .Color import AverageColor
|
||||
from pathlib import Path
|
||||
|
||||
class Samples:
|
||||
def __init__(self,samples_path):
|
||||
samples_files = Path(samples_path).glob("*.jpg")
|
||||
self.samples = [x for x in samples_files if x.is_file()]
|
||||
|
||||
# Generate a unique identifier for the current sample set
|
||||
class SamplesFingerprint():
|
||||
def __init__(self):
|
||||
self.hash = self.create_hash()
|
||||
self.memory = f"mem/{self.hash}"
|
||||
|
||||
# Create hash from sample names and path
|
||||
self.memory = f"memory/{self.hash}.json"
|
||||
self.created = False
|
||||
|
||||
if(not self.hash_exists()):
|
||||
self.save_hash()
|
||||
|
||||
# Checksum by hashing next sample with sum of previous samples
|
||||
def create_hash(self):
|
||||
samples_hash = ""
|
||||
for i in self.samples:
|
||||
samples_hash = hash(i)
|
||||
samples_hash = "I like coffee" # Initialize with padding
|
||||
for i in self.samples_posix:
|
||||
seed = str(samples_hash) + str(i)
|
||||
samples_hash = zlib.crc32(bytes(seed,encoding="utf8"))
|
||||
return samples_hash
|
||||
|
||||
# Test if the current sample set has been saved
|
||||
# Test if sample set has been saved
|
||||
def hash_exists(self):
|
||||
if(Path(self.memory).is_file()):
|
||||
return True
|
||||
return False
|
||||
if(self.created or not Path(self.memory).is_file()):
|
||||
return False
|
||||
|
||||
self.created = True
|
||||
return self.created
|
||||
|
||||
# Save hash to memory location on disk
|
||||
# Save identifier of sample set to disk
|
||||
def save_hash(self):
|
||||
Path(self.memory,"w").touch()
|
||||
Path(self.memory).touch()
|
||||
|
||||
# Load samples from sample set memory file or from samples/
|
||||
class Samples(SamplesFingerprint):
|
||||
def __init__(self,samples_path,force = False):
|
||||
samples_files = Path(samples_path).glob("*.jpg")
|
||||
self.samples_posix = [x for x in samples_files if x.is_file()] # List of PosixPaths
|
||||
self.samples = {} # HEX from color calc algorithm
|
||||
|
||||
super(Samples,self).__init__()
|
||||
|
||||
try:
|
||||
self.load_sample_set()
|
||||
except:
|
||||
self.map_color()
|
||||
self.save_sample_set()
|
||||
|
||||
# Get the pixel value for each sample using a desired algorithm
|
||||
def map_color(self):
|
||||
for i,sample in enumerate(self.samples_posix):
|
||||
color = AverageColor(sample).hex() # Get the average color of a sample as HEX
|
||||
|
||||
self.samples[color] = i
|
||||
print(f"Loaded {i + 1}/{len(self.samples_posix)} samples",end="\r",flush="True")
|
||||
print("")
|
||||
|
||||
# Save calculated sample set data
|
||||
def save_sample_set(self):
|
||||
self.samples = dict(sorted(self.samples.items())) # Sort the dict by chrominance
|
||||
|
||||
with open(self.memory,"w") as f:
|
||||
json.dump(self.samples,f)
|
||||
print(f"Saved sample set with fingerprint: {self.hash}")
|
||||
|
||||
# Load pre-calulated sample set data
|
||||
def load_sample_set(self):
|
||||
with open(self.memory) as f:
|
||||
self.samples = json.load(f)
|
||||
print(f"Loaded {len(self.samples)} samples from set {self.hash}")
|
|
@ -1,4 +0,0 @@
|
|||
#! /bin/bash
|
||||
export PYTHONHASHSEED=0
|
||||
|
||||
python3 main.py
|
23
main.py
23
main.py
|
@ -1,7 +1,22 @@
|
|||
#from classes import Eyedropper
|
||||
from classes.Samples import Samples
|
||||
from classes.Collage import Collage
|
||||
from pathlib import Path
|
||||
|
||||
# Load samples from folder
|
||||
samples = Samples("samples")
|
||||
force = False
|
||||
input_path = "input/"
|
||||
|
||||
print(samples.hash)
|
||||
# Load all JPGs from the "samples/" folder
|
||||
samples = Samples("samples",force)
|
||||
|
||||
input_file = str("input/coffee-resized.jpg")
|
||||
|
||||
collage = Collage(input_file,samples)
|
||||
collage.put(input_file + "_collage.jpg")
|
||||
|
||||
# input_files = Path(input_path).glob("*.jpg")
|
||||
# input_files_posix = [x for x in input_files if x.is_file()] # List of PosixPaths
|
||||
|
||||
# for input_file in input_files_posix:
|
||||
# input_file = str(input_file)
|
||||
# collage = Collage(input_file,samples)
|
||||
# collage.put(input_file + "_collage.jpg")
|
Loading…
Add table
Reference in a new issue