This commit is contained in:
unknown 2016-05-21 12:51:36 -07:00
parent c27e977c8c
commit 3d0a3dc746
34 changed files with 47341 additions and 98 deletions

BIN
ImageFilters/ear.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
ImageFilters/ear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

View file

@ -0,0 +1,97 @@
import PIL.Image
KERNEL_GAUSSIAN_BLUR = [
[1, 2, 1],
[2, 3, 2],
[1, 2, 1],
]
KERNEL_EDGE_DETECTION_H = [
[-2, 0, 2],
[-2, 0, 2],
[-2, 0, 2],
]
def index_to_xy(index, width):
(y, x) = divmod(index, width)
return (x, y)
def xy_to_index(x, y, width):
return (y * width) + x
def apply_filter(old_image, kernel):
kernel_height = len(kernel)
kernel_width = len(kernel[0])
if (kernel_height % 2 != 1) or (kernel_width % 2 != 1):
raise ValueError('Kernel is not of odd size')
if any(len(segment) != kernel_width for segment in kernel):
raise ValueError('Kernel is of inconsistent size')
kernel_center = (kernel_width // 2, kernel_height // 2)
flat_kernel = list(flatten_list(kernel))
lower = min(flat_kernel)
lower = min(0, lower * 255)
upper = max(flat_kernel)
upper = max(255, upper * 255)
print(lower, upper)
(image_width, image_height) = old_image.size
old_pixels = old_image.getdata()
new_pixels = list(old_image.getdata())
for (index, old_pixel) in enumerate(old_pixels):
operation_sum = 0
operation_denominator = 0
(x, y) = index_to_xy(index, image_width)
#print(x, y, index)
for (kernel_y, kernel_row) in enumerate(kernel):
#print(kernel_row)
subject_y = y - (kernel_center[1] - kernel_y)
if subject_y < 0 or subject_y >= image_height:
continue
for (kernel_x, kernel_entry) in enumerate(kernel_row):
subject_x = x - (kernel_center[0] - kernel_x)
if subject_x < 0 or subject_x >= image_width:
continue
subject = old_pixels[xy_to_index(subject_x, subject_y, image_width)]
#print(x, y, subject_x, subject_y, kernel_entry, subject)
operation_sum += kernel_entry * subject
operation_denominator += kernel_entry
operation_denominator = max(1, operation_denominator)
operation_avg = abs(operation_sum / operation_denominator)
#n_operation_avg = int(map_range(operation_avg, lower, upper, 0, 255))
if index % 4096 == 0:
print(x, y, operation_sum, operation_denominator, operation_avg)
#print(y, '/', image_height)
new_pixels[index] = operation_avg
#print(new_pixels)
new_image = PIL.Image.new('L', (old_image.size))
new_image.putdata(new_pixels, 1, 0)
#print(new_pixels)
#print(list(new_image.getdata()))
return new_image
def flatten_list(li):
for element in li:
if hasattr(element, '__iter__'):
yield from flatten_list(element)
else:
yield element
def map_range(x, old_low, old_high, new_low, new_high):
'''
Given a number x in range [old_low, old_high], return corresponding
number in range [new_low, new_high].
'''
if x > old_high or x < old_low:
raise ValueError('%d not in range [%d..%d]' % (x, old_low, old_high))
percentage = (x - old_low) / (old_high - old_low)
y = (percentage * (new_high - new_low)) + new_low
return y
if __name__ == '__main__':
i = PIL.Image.open('ear.jpg')
i = i.convert('L')
i = apply_filter(apply_filter(i, KERNEL_GAUSSIAN_BLUR), KERNEL_EDGE_DETECTION_H)
i.save('ear.png')

View file

@ -0,0 +1,21 @@
Minecraft 3D Vector
===================
Given one, two, or three square images, produce a 3-dimensional rendering of them as a block in glorious SVG.
The svg has two layers. They contain identical shapes, but the lower one is slightly blurred. Although the shapes are aligned pretty well, some rendering engines create a 1px gap between shapes due to rounding. The blurred object helps fill these in. A mask is used to make sure the blur does not cause a halo around the edges. The seams still aren't perfect but they're pretty good, man.
Because each pixel of each face produces two shapes, you should probably stick to small source images.
##Usage:
> mc3dvector.py dirt.png
Creates a block with dirt on all sides
> mc3dvector.py tree_top.png tree_side.png
Creates a block with tree_top on the top, and tree_side on both sides.
> mc3dvector.py dirt.png gravel.png obsidian.png
Creates a block with dirt on top, gravel on the left, and obsidian on the right.
The images do not need to be the same size as each other, but they each must be square.

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 295 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 295 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 295 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 295 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 295 KiB

View file

@ -0,0 +1,200 @@
TEMPLATE_PRIMARY = '''
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg2"
viewBox="0 0 744.09448819 1052.3622047"
height="297mm"
width="210mm" >
<defs id="defs4" >
<filter
id="filter20779"
style="color-interpolation-filters:sRGB" />
<filter
style="color-interpolation-filters:sRGB"
id="filter26457"
x="-0.012382473"
width="1.0247649"
y="-0.011640447"
height="1.0232809" >
<feGaussianBlur
id="feGaussianBlur26459"
stdDeviation="3.2073868" />
</filter>
<mask
id="mask26461"
maskUnits="userSpaceOnUse" >
<g
style="fill:#ffffff;fill-opacity:1;stroke:none"
transform="matrix(1.5199904,0,0,1.5199673,-1612.5515,-1151.5565)"
id="g26463" >
<path
id="path26465"
transform="matrix(0.65789889,0,0,0.65790889,1060.8942,757.62709)"
d="
M 682.8789,715.34763
682.857,337.12518
372.03711,195.5332
61.221001,337.25218
61.220697,715.32812
372.041,856.80518 Z"
style="
opacity:1;
fill:#ffffff;
fill-opacity:1;
fill-rule:nonzero;
stroke:none;
stroke-width:3.34395361;
stroke-linecap:round;
stroke-linejoin:miter;
stroke-miterlimit:4;
stroke-dasharray:none;
stroke-dashoffset:0;
stroke-opacity:1" />
</g>
</mask>
</defs>
<g
id="layer1">
<g
mask="url(#mask26461)"
style="filter:url(#filter26457)" id="g24913" >
<g
transform="matrix(1.0361037,-0.47199221,1.0361037,0.47199221,-801.62397,128.09956)"
style="fill:#000000;fill-opacity:1" >
{elements_top}
</g>
<g
transform="matrix(-1.0361028,0.47161497,0,1.2603136,884.86634,-558.55115)"
style="fill:#000000;fill-opacity:1" >
{elements_left}
</g>
<g
transform="matrix(1.0361028,0.47161497,0,1.2603136,-140.77133,-558.55239)"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" >
{elements_right}
</g>
</g>
<g
mask="url(#mask26461)"
>
<g
transform="matrix(1.0361037,-0.47199221,1.0361037,0.47199221,-801.62397,128.09956)"
style="fill:#000000;fill-opacity:1" >
{elements_top}
</g>
<g
transform="matrix(-1.0361028,0.47161497,0,1.2603136,884.86634,-558.55115)"
style="fill:#000000;fill-opacity:1" >
{elements_left}
</g>
<g
transform="matrix(1.0361028,0.47161497,0,1.2603136,-140.77133,-558.55239)"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" >
{elements_right}
</g>
</g>
</g>
</svg>
'''
TEMPLATE_PIXEL = '''
<path
d="m {x},{y} {pixsize},0 0,{pixsize} -{pixsize},0 z"
id="{id}"
style="opacity:{opacity};fill:#{color}"
/>
'''
import os
import PIL.Image
import random
import sys
# These numbers are MAGIC, and only work because of how the template was made.
SQUARE_WIDTH = 300
START_X = 194.94924
START_Y = 637.82417
def hexadecimal(i):
i = hex(i)[2:]
width = 2 - (len(i) % 2)
if width == 2: width = 0
i = ('0'*width) + i
return i
def mirror(image, direction):
new_image = image.copy()
for y in range(image.size[1]):
for x in range(image.size[0]):
pixel = image.getpixel((x, y))
if direction == 'horizontal':
x = (image.size[0] - 1) - x
elif direction == 'vertical':
y = (image.size[1] - 1) - y
new_image.putpixel((x, y), pixel)
return new_image
def vectorize(filenames):
images = [PIL.Image.open(f) for f in filenames]
if len(images) == 1:
images = [images[0], images[0], images[0]]
elif len(images) == 2:
images = [images[0], images[1], images[1]]
elif len(images) == 3:
pass
else:
raise ValueError('Invalid number of images supplied')
images[0] = images[0].rotate(270)
images[2] = mirror(images[2], 'horizontal')
elements_total = []
for (image_index, image) in enumerate(images):
elements_local = []
width = image.size[0]
step = SQUARE_WIDTH / width
pixsize = SQUARE_WIDTH / width
for y in range(width):
y_point = START_Y + (y * step)
for x in range(width):
x_point = START_X + (x * step)
color = image.getpixel((x, y))
opacity = 1
if isinstance(color, int):
color = hexadecimal(color) * 3
elif isinstance(color, tuple):
if len(color) == 4:
opacity = color[3] / 255
if len(color) >= 3:
color = ''.join(hexadecimal(channel) for channel in color[:3])
element = TEMPLATE_PIXEL.format(
x=x_point,
y=y_point,
opacity=opacity,
pixsize=pixsize,
color=color,
id='face_%d_%d' % (image_index, x + (y*width)))
elements_local.append(element)
elements_total.append(elements_local)
elements_total = [''.join(elements) for elements in elements_total]
image = TEMPLATE_PRIMARY.format(elements_top=elements_total[0], elements_right=elements_total[1], elements_left=elements_total[2])
image = image.strip()
basenames = [os.path.splitext(f)[0] for f in filenames]
outputname = '+'.join(basenames) + '.svg'
print(outputname)
f = open(outputname, 'w')
f.write(image)
f.close()
if __name__ == '__main__':
filenames = sys.argv[1:]
vectorize(filenames)

View file

@ -1,87 +1,107 @@
'''
DOCSTRING='''
OpenDirDL
downloads open directories
Usage:
The basics:
1. Create a database of the directory's files with
> opendirdl digest http://website.com/directory/
2. Enable and disable the files you are interested in with
> opendirdl remove_pattern ".*"
> opendirdl keep_pattern "Daft%20Punk"
> opendirdl remove_pattern "folder\.jpg"
Note the percent-encoded string.
3. Download the enabled files with
> opendirdl download database.db
DIGEST:
Specifics:
digest:
Recursively fetch directories and build a database of file URLs.
> opendirdl digest !clipboard <flags>
> opendirdl digest http://website.com/directory/ <flags>
> opendirdl digest !clipboard <flags>
flags:
-f | --fullscan:
When included, perform HEAD requests on all files, to know the size of the entire directory.
When included, perform HEAD requests on all files, to know the size of
the entire directory.
-db "x.db" | --databasename "x.db":
Use a custom database filename. By default, databases are named after the web domain.
Use a custom database filename. By default, databases are named after
the web domain.
DOWNLOAD:
Download the files whose URLs are enabled in the database.
download:
Download the files whose URLs are Enabled in the database.
> opendirdl download website.com.db <flags>
flags:
-o "x" | --outputdir "x":
Save the files to a custom directory, "x". By default, files are saved to a folder named
after the web domain.
Save the files to a custom directory, "x". By default, files are saved
to a folder named after the web domain.
-ow | --overwrite:
When included, download and overwrite files even if they already exist in the output directory.
When included, download and overwrite files even if they already exist
in the output directory.
-bps 100 | --bytespersecond 100:
Ratelimit yourself to downloading at 100 BYTES per second. The webmaster will appreciate this.
Ratelimit yourself to downloading at 100 BYTES per second.
The webmaster will appreciate this.
KEEP_PATTERN:
Enable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
keep_pattern:
Enable URLs which match a regex pattern. Matches are based on the percent-
encoded strings!
> opendirdl keep_pattern website.com.db ".*"
> opendirdl keep_pattern database.db ".*"
REMOVE_PATTERN:
Disable URLs which match a regex pattern. Matches are based on the percent-encoded strings!
remove_pattern:
Disable URLs which match a regex pattern. Matches are based on the percent-
encoded strings!
> opendirdl remove_pattern website.com.db ".*"
> opendirdl remove_pattern database.db ".*"
LIST_BASENAMES:
List enabled URLs in order of their base filename. This makes it easier to find titles of
interest in a directory that is very scattered or poorly organized.
list_basenames:
List enabled URLs in order of their base filename. This makes it easier to
find titles of interest in a directory that is very scattered or poorly
organized.
> opendirdl list_basenames website.com.db <flags>
flags:
-o "x.txt" | --outputfile "x.txt":
Output the results to a file instead of stdout. This is useful if the filenames contain
special characters that crash Python, or are so long that the console becomes unreadable.
Output the results to a file instead of stdout. This is useful if the
filenames contain special characters that crash Python, or are so long
that the console becomes unreadable.
MEASURE:
Sum up the filesizes of all enabled URLs.
measure:
Sum up the filesizes of all Enabled URLs.
> opendirdl measure website.com.db <flags>
> opendirdl measure database.db <flags>
flags:
-f | --fullscan:
When included, perform HEAD requests on any URL whose size is not known. If this flag is
not included, and some file's size is unkown, you will receive a printed note.
When included, perform HEAD requests when a file's size is not known.
If this flag is not included, and some file's size is unkown, you will
receive a printed note.
'''
# Module names preceeded by two hashes indicate modules that are imported during
# Module names preceeded by `## ~` indicate modules that are imported during
# a function, because they are not used anywhere else and we don't need to waste
# time importing them usually.
import sys
sys.path.append('C:\\git\\else\\ratelimiter'); import ratelimiter
import argparse
## import bs4
## import hashlib
## ~import bs4
## ~import hashlib
import os
## import re
## ~import re
import requests
import shutil
import sqlite3
## tkinter
## ~tkinter
import urllib.parse
FILENAME_BADCHARS = '/\\:*?"<>|'
@ -249,6 +269,7 @@ class Walker:
databasename = databasename.replace(':', '')
self.databasename = databasename
safeprint('Opening %s' % self.databasename)
self.sql = sqlite3.connect(self.databasename)
self.cur = self.sql.cursor()
db_init(self.sql, self.cur)
@ -269,7 +290,7 @@ class Walker:
External links, index sort links, and desktop.ini are discarded.
'''
import bs4
soup = bs4.BeautifulSoup(response.text)
soup = bs4.BeautifulSoup(response.text, 'html.parser')
elements = soup.findAll(tag)
for element in elements:
try:
@ -615,10 +636,10 @@ def filter_pattern(databasename, regex, action='keep', *trash):
should_keep = (keep and contains)
if keep and contains and not current_do_dl:
safeprint('Keeping "%s"' % url)
safeprint('Enabling "%s"' % url)
cur.execute('UPDATE urls SET do_download = 1 WHERE url == ?', [url])
if remove and contains and current_do_dl:
safeprint('Removing "%s"' % url)
safeprint('Disabling "%s"' % url)
cur.execute('UPDATE urls SET do_download = 0 WHERE url == ?', [url])
sql.commit()
@ -735,6 +756,9 @@ def remove_pattern(args):
if __name__ == '__main__':
if listget(sys.argv, 1, '').lower() in ('help', '-h', '--help'):
print(DOCSTRING)
quit()
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
@ -758,7 +782,7 @@ if __name__ == '__main__':
p_list_basenames = subparsers.add_parser('list_basenames')
p_list_basenames.add_argument('databasename')
p_list_basenames.add_argument('-o', '--outputfile', dest='outputfile', default=None)
p_list_basenames.add_argument('outputfile', nargs='?', default=None)
p_list_basenames.set_defaults(func=list_basenames)
p_measure = subparsers.add_parser('measure')

4
RateMeter/README.md Normal file
View file

@ -0,0 +1,4 @@
RateMeter
=========
Provides a `RateMeter` class to measure the speed of something. Create an instance with the appropriate `span`, and call `meter.digest(x)` where `x` is the number of units processed. Later, call `meter.report()` to receive the current speed information.

View file

@ -8,14 +8,10 @@ class RateMeter:
This class is used to calculate a rolling average of
units per second over `span` seconds.
Minimum span is 1 second.
Set `span` to None to calculate unit/s over the lifetime of the object
after the first digest, rather than over a span.
This saves the effort of tracking timestamps. Don't just use a large number!
'''
if span is not None and span < 1:
raise ValueError('Span must be >= 1')
self.sum = 0
self.span = span
@ -23,10 +19,9 @@ class RateMeter:
self.first_digest = None
def digest(self, value):
now = math.ceil(time.time())
now = time.time()
self.sum += value
if self.span is None:
if self.first_digest is None:
self.first_digest = now

View file

@ -4,8 +4,8 @@ import ratemeter
import requests
import time
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.8817502672426312'
METER = ratemeter.RateMeter(span=10)
URL = 'http://cdn.speedof.me/sample32768k.bin?r=0.881750426312'
METER = ratemeter.RateMeter(span=5)
METER_2 = ratemeter.RateMeter(span=None)
class G:
pass
@ -13,7 +13,7 @@ class G:
g = G()
g.total = 0
g.start = None
g.last = int(time.time())
g.last = time.time()
def callback_progress(bytes_downloaded, bytes_total):
if g.start is None:
@ -25,7 +25,7 @@ def callback_progress(bytes_downloaded, bytes_total):
METER.digest(chunk)
METER_2.digest(chunk)
now = round(time.time(), 1)
if now > g.last:
if now > g.last or (bytes_downloaded >= bytes_total):
g.last = now
percent = percent.rjust(9, ' ')
rate = bytestring.bytestring(METER.report()[2]).rjust(15, ' ')
@ -35,5 +35,5 @@ def callback_progress(bytes_downloaded, bytes_total):
#print(METER.report(), METER_2.report())
print(URL)
print('Progress'.rjust(9, ' '), 'bps over 10s'.rjust(15, ' '), 'bps overall'.rjust(15, ' '), 'elapsed'.rjust(10, ' '))
print('Progress'.rjust(9, ' '), 'bps over 5s'.rjust(15, ' '), 'bps overall'.rjust(15, ' '), 'elapsed'.rjust(10, ' '))
downloady.download_file(URL, 'nul', callback_progress=callback_progress)

6
Ratelimiter/README.md Normal file
View file

@ -0,0 +1,6 @@
Ratelimiter
===========
Provides a `Ratelimiter` class to regulate timing. Create an instance with the appropriate allowance and timing rules, then just call `limiter.limit()` in your loop.
Note that allowance=10, period=10 is not the same as allowance=1, period=1. The first allows for more "burstiness" because all 10 operations can happen in the first second, as long as you wait for the other 9.

View file

@ -2,10 +2,10 @@ import time
class Ratelimiter:
def __init__(self, allowance_per_period, period, operation_cost=1, mode='sleep'):
def __init__(self, allowance_per_period, period=1, operation_cost=1, mode='sleep'):
'''
allowance_per_period:
The number of operations we can perform per `period` seconds.
Our spending balance per `period` seconds.
period:
The number of seconds over which we can perform `allowance_per_period` operations.
@ -15,12 +15,17 @@ class Ratelimiter:
Pass a `cost` parameter to `self.limit` to use a nondefault value.
mode:
'sleep': If we do not have the balance for an operation, sleep until we do.
Return True every time.
'reject': If we do not have the balance for an operation, return False.
'sleep':
If we do not have the balance for an operation, sleep until we do.
Return True every time.
'reject':
If we do not have the balance for an operation, return False.
The cost is not subtracted, so hopefully we have enough next time.
'''
if mode not in ('sleep', 'reject'):
raise ValueError('Invalid mode %s' % repr(mode))
self.allowance_per_period = allowance_per_period
self.period = period
self.operation_cost = operation_cost
@ -28,29 +33,34 @@ class Ratelimiter:
self.last_operation = time.time()
self.balance = 0
self.gain_rate = allowance_per_period / period
@property
def gain_rate(self):
return self.allowance_per_period / self.period
def limit(self, cost=None):
'''
See the main class docstring for info about cost and mode behavior.
'''
if cost is None:
cost = self.operation_cost
timediff = time.time() - self.last_operation
self.balance += timediff * self.gain_rate
time_diff = time.time() - self.last_operation
self.balance += time_diff * self.gain_rate
self.balance = min(self.balance, self.allowance_per_period)
successful = False
deficit = cost - self.balance
if deficit > 0 and self.mode == 'sleep':
time_needed = (deficit / self.gain_rate)
#print(self.balance, deficit, 'Need to sleep %f' % time_needed)
time.sleep(time_needed)
self.balance = cost
#print(self.balance)
if self.balance >= cost:
#print('pass')
self.balance -= cost
successful = True
succesful = True
else:
if self.mode == 'reject':
succesful = False
else:
deficit = cost - self.balance
time_needed = deficit / self.gain_rate
time.sleep(time_needed)
self.balance = 0
succesful = True
self.last_operation = time.time()
return successful
return succesful

270
SinWave/sinwave.py Normal file
View file

@ -0,0 +1,270 @@
import math
import random
import string
import threading
import time
import tkinter
# Nice patterns
# 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 315, 330, 345
# 0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330
# 0, 15, 30, 45, 60, 180, 195, 210, 225, 240
# 0, 90, 180, 270
# ░▒▓
SCREEN_WIDTH = 114
DEFAULT_LINE = {
'character': '#',
'degree': 0,
'degree_step': 1,
'horizontal_offset': 0,
'line_thickness': 1,
'original_degree': 0,
'damping': 3,
'depth': 0,
}
variables = {
'clock': 0,
'frames':[],
'delay': 0.01,
'lines':[
]
}
class LineControl(tkinter.Frame):
def __init__(self, master, line, *args, **kwargs):
tkinter.Frame.__init__(self, master, *args, **kwargs)
self.line = line
for x in range(4):
self.columnconfigure(x, minsize=40)
row = 0
tkinter.Button(self, text='\u21bb', command=self.refresh).grid(row=row, column=0, sticky='news')
tkinter.Button(self, text='Clone', command=self.clone).grid(row=row, column=1, columnspan=2, sticky='news')
tkinter.Button(self, text='X', command=self.suicide).grid(row=row, column=3, sticky='news')
row += 1
tkinter.Label(self, text='Degree:').grid(row=row, column=0, sticky='e')
self.entry_degree = tkinter.Entry(self, width=3)
self.entry_degree.insert(0, self.line['original_degree'])
self.entry_degree.grid(row=row, column=1, sticky='news')
tkinter.Button(self, text='Set', command=self.apply_degree).grid(row=row, column=2, sticky='news')
row += 1
def create_buttonpair(key, row):
minus = tkinter.Button(self, text='-', command=lambda *a: self.step_variable(key, -1, self.value_labels[key]))
minus.grid(row=row, column=1, sticky='news')
plus = tkinter.Button(self, text='+', command=lambda *a: self.step_variable(key, +1, self.value_labels[key]))
plus.grid(row=row, column=2, sticky='news')
self.value_labels = {}
tkinter.Label(self, text='Step:').grid(row=row, column=0, sticky='e')
create_buttonpair('degree_step', row)
self.value_labels['degree_step'] = tkinter.Label(self, text=str(self.line['degree_step']))
self.value_labels['degree_step'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Thick:').grid(row=row, column=0, sticky='e')
create_buttonpair('line_thickness', row)
self.value_labels['line_thickness'] = tkinter.Label(self, text=str(self.line['line_thickness']))
self.value_labels['line_thickness'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Offset:').grid(row=row, column=0, sticky='e')
create_buttonpair('horizontal_offset', row)
self.value_labels['horizontal_offset'] = tkinter.Label(self, text=str(self.line['horizontal_offset']))
self.value_labels['horizontal_offset'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Dampen:').grid(row=row, column=0, sticky='e')
create_buttonpair('damping', row)
self.value_labels['damping'] = tkinter.Label(self, text=str(self.line['damping']))
self.value_labels['damping'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Depth:').grid(row=row, column=0, sticky='e')
create_buttonpair('depth', row)
self.value_labels['depth'] = tkinter.Label(self, text=str(self.line['depth']))
self.value_labels['depth'].grid(row=row, column=3)
row += 1
tkinter.Label(self, text='Char:').grid(row=row, column=0, sticky='e')
self.entry_character = tkinter.Entry(self, width=2, font=('Consolas', 12))
self.entry_character.grid(row=row, column=1, sticky='news')
self.entry_character.insert(0, self.line['character'])
tkinter.Button(self, text='Set', command=self.apply_character).grid(row=row, column=2, sticky='news')
row += 1
def apply_character(self, *trash):
char = self.entry_character.get()
try:
assert len(char) == 1
except:
return
self.line['character'] = char
def apply_degree(self, *trash):
value = self.entry_degree.get()
try:
value = int(value)
except ValueError:
return
self.line['original_degree'] = value % 360
self.refresh()
regrid_frames()
def clone(self, *trash):
line = self.line.copy()
create_line_frame(line=line)
regrid_frames()
def refresh(self, *trash):
self.line['degree'] = self.line['original_degree'] + (self.line['degree_step'] * variables['clock'])
def step_variable(self, key, direction, label):
value = self.line[key]
value += direction
if key == 'line_thickness':
value = max(value, 0)
elif key == 'horizontal_offset':
value = value % SCREEN_WIDTH
elif key == 'delay':
value = max(value, 0.001)
elif key in ('damping', 'depth'):
value = max(value, 0)
value = min(value, int(SCREEN_WIDTH / 2))
self.line[key] = value
label.configure(text=str(value)[:5])
def suicide(self, *trash):
unregister_line(self.line)
variables['frames'].remove(self)
self.grid_forget()
regrid_frames()
def create_line(degree_offset, **kwargs):
line = DEFAULT_LINE.copy()
line.update(kwargs)
line['original_degree'] = degree_offset
line['degree'] = (variables['clock'] + degree_offset) % 360
# Line ID exists solely to make sure that even clones are unique.
line['id'] = random.getrandbits(32)
return line
def create_line_frame(offset=None, line=None):
if line is None:
if offset is None:
offset = entry_add.get()
offset = offset.replace(' ', '')
offset = offset.split(',')
try:
offset = [int(o) for o in offset]
except ValueError:
return
entry_add.delete(0, 'end')
lines = []
for x in offset:
line = create_line(x)
lines.append(line)
else:
line['id'] = random.getrandbits(32)
lines = [line]
for line in lines:
register_line(line)
frame = LineControl(t, line, relief='groove', borderwidth=2)
variables['frames'].append(frame)
regrid_frames()
def map_range(x, old_low, old_high, new_low, new_high):
'''
Given a number x in range [old_low, old_high], return corresponding
number in range [new_low, new_high].
'''
if x > old_high or x < old_low:
raise ValueError('%d not in range [%d..%d]' % (x, old_low, old_high))
percentage = (x - old_low) / (old_high - old_low)
y = (percentage * (new_high - new_low)) + new_low
return y
def position_from_degree(d, damping_units):
rad = math.radians(d)
sin = math.sin(rad)
sin = map_range(sin, -1, 1, 0, 1)
position = sin * (SCREEN_WIDTH - 1)
position = map_range(position, 0, SCREEN_WIDTH, damping_units, SCREEN_WIDTH-damping_units)
#position = (position * (1- (2 * damping_percent))) + damping_units
position = round(position)
position = position % SCREEN_WIDTH
return position
def print_loop():
while True:
next_frame = time.time() + variables['delay']
variables['clock'] = (variables['clock'] + 1) % 360
screen = ([' '] * SCREEN_WIDTH)
z_indexes = {}
for line_vars in variables['lines']:
start_degree = line_vars['degree']
end_degree = (start_degree + line_vars['degree_step']) % 360
line_vars['degree'] = end_degree
damping = line_vars['damping']
start_pos = position_from_degree(start_degree, damping)
end_pos = position_from_degree(end_degree, damping)
(start_pos, end_pos) = sorted([start_pos, end_pos])
start_pos -= line_vars['line_thickness']
end_pos += line_vars['line_thickness']
mini_step = line_vars['degree_step'] / max((end_pos - start_pos), 1)
for (index, position) in enumerate(range(start_pos, end_pos+1)):
position = (position + line_vars['horizontal_offset']) % SCREEN_WIDTH
this_rads = math.radians(start_degree + (mini_step * index))
z_index = math.cos(this_rads)
z_index = map_range(z_index, -1, 1, line_vars['depth'], SCREEN_WIDTH-line_vars['depth'])
#print(line_vars['character'], z_index)
if z_index > z_indexes.get(position, -2):
screen[position] = line_vars['character']
z_indexes[position] = z_index
print('%03d' % variables['clock'], ''.join(screen))
delay = max(next_frame - time.time(), 0)
time.sleep(delay)
def register_line(line):
variables['lines'].append(line)
def regrid_frames():
frames = variables['frames']
#frames.sort(key=lambda x: x.line['original_degree'])
for (index, frame) in enumerate(frames):
(row, column) = divmod(index, 4)
row += 1
frame.grid(row=row, column=column)
def unregister_line(line):
variables['lines'].remove(line)
t = tkinter.Tk()
frame_add = tkinter.Frame(t)
entry_add = tkinter.Entry(frame_add)
entry_add.grid(row=0, column=0)
tkinter.Button(frame_add, text='+', command=create_line_frame).grid(row=0, column=1)
frame_add.grid(row=0, column=0)
frame_delay = tkinter.Frame(t)
tkinter.Label(frame_delay, text='Speed:')
thread = threading.Thread(target=print_loop)
thread.daemon=True
thread.start()
create_line_frame([0])
t.mainloop()

View file

@ -1,7 +1,16 @@
Steganographic
==============
2015 01 15:
2016 05 12:
Now pads the remaining pixels with random data. Previously, the fact that the secret
file resided in only as many pixels as it needed meant there was a sudden decrease in
visual noise when the secret ended. Now there is noise throughout.
Your secret file should resemble random noise for this to be effective. Thus
encryption is recommended.
This does not change decoding, because the decoder only reads as many bytes as
needed.
2016 01 15:
Now supports variable "bitness", the number of bits per color channel to overwrite.
Previously, bitness was always 1, to maximize transparency.
Now, bitness can be 1-8 to favor transparency or information density.

View file

@ -178,10 +178,6 @@ def set_bit(number, index, newvalue):
#### ## #### #### #### #### #### #### #### #### #### ##
############## #### #### ######## ###### ########## ##############
def encode(imagefilename, secretfilename, bitness=1):
global image
global pixel
global pixel_index
global channel_index
pixel_index = 0
channel_index = 0
@ -198,18 +194,18 @@ def encode(imagefilename, secretfilename, bitness=1):
image = Image.open(imagefilename)
image_steg = BitsToImage(image, bitness)
totalpixels = image.size[0] * image.size[1]
if totalpixels < HEADER_SIZE:
total_pixels = image.size[0] * image.size[1]
if total_pixels < HEADER_SIZE:
raise StegError('Image cannot have fewer than %d pixels. They are used to store Secret\'s length' % HEADER_SIZE)
secret_extension = os.path.splitext(secretfilename)[1][1:]
secret_content_length = (secret_size) + (len(secret_extension)) + 1
requiredpixels = math.ceil(((secret_content_length * 8) + 32) / (3 * bitness))
if totalpixels < requiredpixels:
if total_pixels < requiredpixels:
raise StegError('Image does not have enough pixels to store the Secret. '
'Must have at least %d pixels' % requiredpixels)
print('%d pixels available, %d required' % (totalpixels, requiredpixels))
print('%d pixels available, %d required' % (total_pixels, requiredpixels))
# --> YOU ARE HERE <--
@ -226,23 +222,40 @@ def encode(imagefilename, secretfilename, bitness=1):
# Write the secret data
bytes_written = 0
done = False
secretfile = open(secretfilename, 'rb')
while not done:
if bytes_written % 1024 == 0:
percentage = (bytes_written + 1) / secret_size
percentage = '%07.3f%%\r' % (100 * percentage)
print(percentage, end='')
one_complete_file = False
bytes = secretfile.read(FILE_READ_SIZE)
# Yes, we're assuming the whole file fits in memory.
secret_file = open(secretfilename, 'rb')
secret_data = secret_file.read()
secret_data_size = len(secret_data)
secret_index = 0
for pixel_number in range(total_pixels):
if pixel_number % 4 == 0:
#percentage = (bytes_written + 1) / secret_size
#percentage = '%07.3f%%\r' % (100 * percentage)
#print(percentage, end='')
print(pixel_number)
done = len(bytes) == 0
secret_chunk = secret_data[secret_index:secret_index+FILE_READ_SIZE]
secret_index += FILE_READ_SIZE
if len(secret_chunk) < FILE_READ_SIZE:
one_complete_file = True
#secret_index = 0
rs = FILE_READ_SIZE - len(secret_chunk)
secret_chunk += os.urandom(rs)
#secret_chunk += secret_data[secret_index:secret_index+rs]
bytes = list(bytes)
bytes = [binary(byte) for byte in bytes]
bytes_written += len(bytes)
bytes = ''.join(bytes)
image_steg.write(bytes)
secret_chunk = list(secret_chunk)
secret_chunk = [binary(byte) for byte in secret_chunk]
bytes_written += len(secret_chunk)
secret_chunk = ''.join(secret_chunk)
try:
image_steg.write(secret_chunk)
except IndexError:
if one_complete_file:
break
else:
raise
# haha
print('100.000%')
@ -271,7 +284,6 @@ def encode(imagefilename, secretfilename, bitness=1):
#### #### #### ## #### #### #### #### #### #### #### ##
########## ############## ######## ###### ########## ##############
def decode(imagefilename, bitness=1):
print('Extracting content from "%s"' % imagefilename)
image = Image.open(imagefilename)
image_steg = ImageToBits(image, bitness)
@ -331,16 +343,16 @@ if __name__ == '__main__':
command = listget(sys.argv, 1, '').lower()
if command not in ['encode', 'decode']:
print('Usage:')
print('> steganographic.py encode imagefilename.png secretfilename.ext')
print('> steganographic.py decode lacedimagename.png')
print('> steganographic.py encode imagefilename.png secretfilename.ext bitness')
print('> steganographic.py decode lacedimagename.png bitness')
quit()
imagefilename = sys.argv[2]
if command == 'encode':
secretfilename = sys.argv[3]
bitness = int(listget(sys.argv, 4, 1))
bitness = int(sys.argv[4])
encode(imagefilename, secretfilename, bitness)
else:
bitness = int(listget(sys.argv, 3, 1))
bitness = int(sys.argv[3])
decode(imagefilename, bitness)