mirror of
https://codeberg.org/vlw/collage.git
synced 2025-09-13 23:23:41 +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__
|
__pycache__
|
||||||
|
__sample__.json
|
||||||
|
|
||||||
input/*
|
input/*
|
||||||
samples/*
|
samples/*
|
||||||
mem/*
|
memory/*
|
||||||
|
|
||||||
!*/.placeholder
|
!*/.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
|
from pathlib import Path
|
||||||
|
|
||||||
class Samples:
|
# Generate a unique identifier for the current sample set
|
||||||
def __init__(self,samples_path):
|
class SamplesFingerprint():
|
||||||
samples_files = Path(samples_path).glob("*.jpg")
|
def __init__(self):
|
||||||
self.samples = [x for x in samples_files if x.is_file()]
|
|
||||||
|
|
||||||
self.hash = self.create_hash()
|
self.hash = self.create_hash()
|
||||||
self.memory = f"mem/{self.hash}"
|
self.memory = f"memory/{self.hash}.json"
|
||||||
|
self.created = False
|
||||||
# Create hash from sample names and path
|
|
||||||
|
if(not self.hash_exists()):
|
||||||
|
self.save_hash()
|
||||||
|
|
||||||
|
# Checksum by hashing next sample with sum of previous samples
|
||||||
def create_hash(self):
|
def create_hash(self):
|
||||||
samples_hash = ""
|
samples_hash = "I like coffee" # Initialize with padding
|
||||||
for i in self.samples:
|
for i in self.samples_posix:
|
||||||
samples_hash = hash(i)
|
seed = str(samples_hash) + str(i)
|
||||||
|
samples_hash = zlib.crc32(bytes(seed,encoding="utf8"))
|
||||||
return samples_hash
|
return samples_hash
|
||||||
|
|
||||||
# Test if the current sample set has been saved
|
# Test if sample set has been saved
|
||||||
def hash_exists(self):
|
def hash_exists(self):
|
||||||
if(Path(self.memory).is_file()):
|
if(self.created or not Path(self.memory).is_file()):
|
||||||
return True
|
return False
|
||||||
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):
|
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.Samples import Samples
|
||||||
|
from classes.Collage import Collage
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
# Load samples from folder
|
force = False
|
||||||
samples = Samples("samples")
|
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