init commit
This commit is contained in:
BIN
modules/__pycache__/ansi.cpython-35.pyc
Normal file
BIN
modules/__pycache__/ansi.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/cirque.cpython-35.pyc
Normal file
BIN
modules/__pycache__/cirque.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/graphics_util.cpython-35.pyc
Normal file
BIN
modules/__pycache__/graphics_util.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/img2txt.cpython-35.pyc
Normal file
BIN
modules/__pycache__/img2txt.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/maketxt.cpython-35.pyc
Normal file
BIN
modules/__pycache__/maketxt.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/meme.cpython-35.pyc
Normal file
BIN
modules/__pycache__/meme.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/trump.cpython-35.pyc
Normal file
BIN
modules/__pycache__/trump.cpython-35.pyc
Normal file
Binary file not shown.
BIN
modules/ansi.pyc
Normal file
BIN
modules/ansi.pyc
Normal file
Binary file not shown.
1
modules/data.json
Normal file
1
modules/data.json
Normal file
File diff suppressed because one or more lines are too long
16
modules/graphics_util.py
Normal file
16
modules/graphics_util.py
Normal file
@@ -0,0 +1,16 @@
|
||||
def alpha_blend(src, dst):
|
||||
# Does not assume that dst is fully opaque
|
||||
# See https://en.wikipedia.org/wiki/Alpha_compositing - section on "Alpha Blending"
|
||||
src_multiplier = (src[3] / 255.0)
|
||||
dst_multiplier = (dst[3] / 255.0) * (1 - src_multiplier)
|
||||
result_alpha = src_multiplier + dst_multiplier
|
||||
if result_alpha == 0: # special case to prevent div by zero below
|
||||
return (0, 0, 0, 0)
|
||||
else:
|
||||
return (
|
||||
int(((src[0] * src_multiplier) + (dst[0] * dst_multiplier)) / result_alpha),
|
||||
int(((src[1] * src_multiplier) + (dst[1] * dst_multiplier)) / result_alpha),
|
||||
int(((src[2] * src_multiplier) + (dst[2] * dst_multiplier)) / result_alpha),
|
||||
int(result_alpha * 255)
|
||||
)
|
||||
|
||||
BIN
modules/graphics_util.pyc
Normal file
BIN
modules/graphics_util.pyc
Normal file
Binary file not shown.
BIN
modules/image.jpg
Normal file
BIN
modules/image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
310
modules/img2txt.py
Normal file
310
modules/img2txt.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
|
||||
("""
|
||||
Usage:
|
||||
img2txt.py <imgfile> [--maxLen=<n>] [--fontSize=<n>] [--color] [--ansi]"""
|
||||
"""[--bgcolor=<#RRGGBB>] [--targetAspect=<n>] [--antialias] [--dither]
|
||||
img2txt.py (-h | --help)
|
||||
|
||||
Options:
|
||||
-h --help show this screen.
|
||||
--ansi output an ANSI rendering of the image
|
||||
--color output a colored HTML rendering of the image.
|
||||
--antialias causes any resizing of the image to use antialiasing
|
||||
--dither dither the colors to web palette. Useful when converting
|
||||
images to ANSI (which has a limited color palette)
|
||||
--fontSize=<n> sets font size (in pixels) when outputting HTML,
|
||||
default: 7
|
||||
--maxLen=<n> resize image so that larger of width or height matches
|
||||
maxLen, default: 100px
|
||||
--bgcolor=<#RRGGBB> if specified, is blended with transparent pixels to
|
||||
produce the output. In ansi case, if no bgcolor set, a
|
||||
fully transparent pixel is not drawn at all, partially
|
||||
transparent pixels drawn as if opaque
|
||||
--targetAspect=<n> resize image to this ratio of width to height. Default is
|
||||
1.0 (no resize). For a typical terminal where height of a
|
||||
character is 2x its width, you might want to try 0.5 here
|
||||
""")
|
||||
|
||||
import sys, os
|
||||
from docopt import docopt
|
||||
from PIL import Image
|
||||
sys.path.append(os.getcwd())
|
||||
from graphics_util import alpha_blend
|
||||
|
||||
def HTMLColorToRGB(colorstring):
|
||||
""" convert #RRGGBB to an (R, G, B) tuple """
|
||||
colorstring = colorstring.strip()
|
||||
if colorstring[0] == '#':
|
||||
colorstring = colorstring[1:]
|
||||
if len(colorstring) != 6:
|
||||
raise ValueError(
|
||||
"input #{0} is not in #RRGGBB format".format(colorstring))
|
||||
r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
|
||||
r, g, b = [int(n, 16) for n in (r, g, b)]
|
||||
return (r, g, b)
|
||||
|
||||
|
||||
def generate_HTML_for_image(pixels, width, height):
|
||||
|
||||
string = ""
|
||||
# first go through the height, otherwise will rotate
|
||||
for h in range(height):
|
||||
for w in range(width):
|
||||
|
||||
rgba = pixels[w, h]
|
||||
|
||||
# TODO - could optimize output size by keeping span open until we
|
||||
# hit end of line or different color/alpha
|
||||
# Could also output just rgb (not rgba) when fully opaque - if
|
||||
# fully opaque is prevalent in an image
|
||||
# those saved characters would add up
|
||||
string += ("<span style=\"color:rgba({0}, {1}, {2}, {3});\">"
|
||||
"▇</span>").format(
|
||||
rgba[0], rgba[1], rgba[2], rgba[3] / 255.0)
|
||||
|
||||
string += "\n"
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def generate_grayscale_for_image(pixels, width, height, bgcolor):
|
||||
|
||||
# grayscale
|
||||
color = "MNHQ$OC?7>!:-;. "
|
||||
|
||||
string = ""
|
||||
# first go through the height, otherwise will rotate
|
||||
for h in range(height):
|
||||
for w in range(width):
|
||||
|
||||
rgba = pixels[w, h]
|
||||
|
||||
# If partial transparency and we have a bgcolor, combine with bg
|
||||
# color
|
||||
if rgba[3] != 255 and bgcolor is not None:
|
||||
rgba = alpha_blend(rgba, bgcolor)
|
||||
|
||||
# Throw away any alpha (either because bgcolor was partially
|
||||
# transparent or had no bg color)
|
||||
# Could make a case to choose character to draw based on alpha but
|
||||
# not going to do that now...
|
||||
rgb = rgba[:3]
|
||||
|
||||
string += color[int(sum(rgb) / 3.0 / 256.0 * 16)]
|
||||
|
||||
string += "\n"
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def load_and_resize_image(imgname, antialias, maxLen, aspectRatio):
|
||||
|
||||
if aspectRatio is None:
|
||||
aspectRatio = 1.0
|
||||
|
||||
img = Image.open(imgname)
|
||||
|
||||
# force image to RGBA - deals with palettized images (e.g. gif) etc.
|
||||
if img.mode != 'RGBA':
|
||||
img = img.convert('RGBA')
|
||||
|
||||
# need to change the size of the image?
|
||||
if maxLen is not None or aspectRatio != 1.0:
|
||||
|
||||
native_width, native_height = img.size
|
||||
|
||||
new_width = native_width
|
||||
new_height = native_height
|
||||
|
||||
# First apply aspect ratio change (if any) - just need to adjust one axis
|
||||
# so we'll do the height.
|
||||
if aspectRatio != 1.0:
|
||||
new_height = int(float(aspectRatio) * new_height)
|
||||
|
||||
# Now isotropically resize up or down (preserving aspect ratio) such that
|
||||
# longer side of image is maxLen
|
||||
if maxLen is not None:
|
||||
rate = float(maxLen) / max(new_width, new_height)
|
||||
new_width = int(rate * new_width)
|
||||
new_height = int(rate * new_height)
|
||||
|
||||
if native_width != new_width or native_height != new_height:
|
||||
img = img.resize((new_width, new_height), Image.ANTIALIAS if antialias else Image.NEAREST)
|
||||
|
||||
return img
|
||||
|
||||
|
||||
def floydsteinberg_dither_to_web_palette(img):
|
||||
|
||||
# Note that alpha channel is thrown away - if you want to keep it you need to deal with it yourself
|
||||
#
|
||||
# Here's how it works:
|
||||
# 1. Convert to RGB if needed - we can't go directly from RGBA because Image.convert will not dither in this case
|
||||
# 2. Convert to P(alette) mode - this lets us kick in dithering.
|
||||
# 3. Convert back to RGBA, which is where we want to be
|
||||
#
|
||||
# Admittedly converting back and forth requires more memory than just dithering directly
|
||||
# in RGBA but that's how the library works and it isn't worth writing it ourselves
|
||||
# or looking for an alternative given current perf needs.
|
||||
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGB')
|
||||
img = img.convert(mode="P", matrix=None, dither=Image.FLOYDSTEINBERG, palette=Image.WEB, colors=256)
|
||||
img = img.convert('RGBA')
|
||||
return img
|
||||
|
||||
|
||||
def dither_image_to_web_palette(img, bgcolor):
|
||||
|
||||
if bgcolor is not None:
|
||||
# We know the background color so flatten the image and bg color together, thus getting rid of alpha
|
||||
# This is important because as discussed below, dithering alpha doesn't work correctly.
|
||||
img = Image.alpha_composite(Image.new("RGBA", img.size, bgcolor), img) # alpha blend onto image filled with bgcolor
|
||||
dithered_img = floydsteinberg_dither_to_web_palette(img)
|
||||
else:
|
||||
|
||||
"""
|
||||
It is not possible to correctly dither in the presence of transparency without knowing the background
|
||||
that the image will be composed onto. This is because dithering works by propagating error that is introduced
|
||||
when we select _available_ colors that don't match the _desired_ colors. Without knowing the final color value
|
||||
for a pixel, it is not possible to compute the error that must be propagated FROM it. If a pixel is fully or
|
||||
partially transparent, we must know the background to determine the final color value. We can't even record
|
||||
the incoming error for the pixel, and then later when/if we know the background compute the full error and
|
||||
propagate that, because that error needs to propagate into the original color selection decisions for the other
|
||||
pixels. Those decisions absorb error and are lossy. You can't later apply more error on top of those color
|
||||
decisions and necessarily get the same results as applying that error INTO those decisions in the first place.
|
||||
|
||||
So having established that we could only handle transparency correctly at final draw-time, shouldn't we just
|
||||
dither there instead of here? Well, if we don't know the background color here we don't know it there either.
|
||||
So we can either not dither at all if we don't know the bg color, or make some approximation. We've chosen
|
||||
the latter. We'll handle it here to make the drawing code simpler. So what is our approximation? We basically
|
||||
just ignore any color changes dithering makes to pixels that have transparency, and prevent any error from being
|
||||
propagated from those pixels. This is done by setting them all to black before dithering (using an exact-match
|
||||
color in Floyd Steinberg dithering with a web-safe-palette will never cause a pixel to receive enough inbound error
|
||||
to change color and thus will not propagate error), and then afterwards we set them back to their original values.
|
||||
This means that transparent pixels are essentially not dithered - they ignore (and absorb) inbound error but they
|
||||
keep their original colors. We could alternately play games with the alpha channel to try to propagate the error
|
||||
values for transparent pixels through to when we do final drawing but it only works in certain cases and just isn't
|
||||
worth the effort (which involves writing the dithering code ourselves for one thing).
|
||||
"""
|
||||
|
||||
# Force image to RGBA if it isn't already - simplifies the rest of the code
|
||||
if img.mode != 'RGBA':
|
||||
img = img.convert('RGBA')
|
||||
|
||||
rgb_img = img.convert('RGB')
|
||||
|
||||
orig_pixels = img.load()
|
||||
rgb_pixels = rgb_img.load()
|
||||
width, height = img.size
|
||||
|
||||
for h in range(height): # set transparent pixels to black
|
||||
for w in range(width):
|
||||
if (orig_pixels[w, h])[3] != 255:
|
||||
rgb_pixels[w, h] = (0, 0, 0) # bashing in a new value changes it!
|
||||
|
||||
dithered_img = floydsteinberg_dither_to_web_palette(rgb_img)
|
||||
|
||||
dithered_pixels = dithered_img.load() # must do it again
|
||||
|
||||
for h in range(height): # restore original RGBA for transparent pixels
|
||||
for w in range(width):
|
||||
if (orig_pixels[w, h])[3] != 255:
|
||||
dithered_pixels[w, h] = orig_pixels[w, h] # bashing in a new value changes it!
|
||||
|
||||
return dithered_img
|
||||
|
||||
|
||||
|
||||
def img2str(memePath):
|
||||
|
||||
imgname = memePath
|
||||
|
||||
maxLen = 100
|
||||
|
||||
clr = None
|
||||
|
||||
fontSize = 7
|
||||
|
||||
bgcolor = None
|
||||
|
||||
antialias = None
|
||||
|
||||
dither = None
|
||||
|
||||
target_aspect_ratio = .8
|
||||
|
||||
try:
|
||||
maxLen = float(maxLen)
|
||||
except:
|
||||
maxLen = 100.0 # default maxlen: 100px
|
||||
|
||||
try:
|
||||
fontSize = int(fontSize)
|
||||
except:
|
||||
fontSize = 7
|
||||
|
||||
try:
|
||||
# add fully opaque alpha value (255)
|
||||
bgcolor = HTMLColorToRGB(bgcolor) + (255, )
|
||||
except:
|
||||
bgcolor = None
|
||||
|
||||
try:
|
||||
target_aspect_ratio = float(target_aspect_ratio)
|
||||
except:
|
||||
target_aspect_ratio = 1.0 # default target_aspect_ratio: 1.0
|
||||
|
||||
try:
|
||||
img = load_and_resize_image(imgname, antialias, maxLen, target_aspect_ratio)
|
||||
except IOError:
|
||||
exit("File not found: " + imgname)
|
||||
|
||||
# Dither _after_ resizing
|
||||
if dither:
|
||||
img = dither_image_to_web_palette(img, bgcolor)
|
||||
|
||||
# get pixels
|
||||
pixel = img.load()
|
||||
|
||||
width, height = img.size
|
||||
|
||||
if clr:
|
||||
# TODO - should handle bgcolor - probably by setting it as BG on
|
||||
# the CSS for the pre
|
||||
string = generate_HTML_for_image(pixel, width, height)
|
||||
else:
|
||||
string = generate_grayscale_for_image(
|
||||
pixel, width, height, bgcolor)
|
||||
# wrap with html
|
||||
|
||||
template = """<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style type="text/css" media="all">
|
||||
pre {
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
font-family: 'Menlo', 'Courier New', 'Consola';
|
||||
line-height: 1.0;
|
||||
font-size: %dpx;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre>%s</pre>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
return string
|
||||
|
||||
sys.stdout.flush()
|
||||
BIN
modules/img2txt.pyc
Normal file
BIN
modules/img2txt.pyc
Normal file
Binary file not shown.
51
modules/maketxt.py
Normal file
51
modules/maketxt.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from PIL import Image
|
||||
import requests
|
||||
from io import BytesIO
|
||||
|
||||
ASCII_CHARS = [ '#', '?', '%', '.', 'S', '+', '.', '*', ':', ',', '@']
|
||||
|
||||
def scale_image(image, new_width=100):
|
||||
"""Resizes an image preserving the aspect ratio.
|
||||
"""
|
||||
(original_width, original_height) = image.size
|
||||
aspect_ratio = original_height/float(original_width)
|
||||
new_height = int(aspect_ratio * new_width)
|
||||
|
||||
new_image = image.resize((new_width, new_height))
|
||||
return new_image
|
||||
|
||||
def convert_to_grayscale(image):
|
||||
return image.convert('L')
|
||||
|
||||
def map_pixels_to_ascii_chars(image, range_width=25):
|
||||
"""Maps each pixel to an ascii char based on the range
|
||||
in which it lies.
|
||||
|
||||
0-255 is divided into 11 ranges of 25 pixels each.
|
||||
"""
|
||||
|
||||
pixels_in_image = list(image.getdata())
|
||||
pixels_to_chars = [ASCII_CHARS[pixel_value/range_width] for pixel_value in
|
||||
pixels_in_image]
|
||||
|
||||
return "".join(pixels_to_chars)
|
||||
|
||||
def convert_image_to_ascii(image, new_width=100):
|
||||
image = scale_image(image)
|
||||
image = convert_to_grayscale(image)
|
||||
|
||||
pixels_to_chars = map_pixels_to_ascii_chars(image)
|
||||
len_pixels_to_chars = len(pixels_to_chars)
|
||||
|
||||
image_ascii = [pixels_to_chars[index: index + new_width] for index in
|
||||
xrange(0, len_pixels_to_chars, new_width)]
|
||||
|
||||
return "\n".join(image_ascii)
|
||||
|
||||
def handle_image_conversion(image_filepath):
|
||||
|
||||
response = requests.get(image_filepath)
|
||||
image = Image.open(BytesIO(response.content))
|
||||
|
||||
image_ascii = convert_image_to_ascii(image)
|
||||
return(image_ascii)
|
||||
35
modules/meme.py
Normal file
35
modules/meme.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import sopel.module
|
||||
import json, random, sys, os
|
||||
sys.path.append(os.getcwd())
|
||||
from img2txt import *
|
||||
import urllib.request
|
||||
|
||||
@sopel.module.commands('meme')
|
||||
def meme(bot, trigger):
|
||||
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
|
||||
url = "https://api.imgflip.com/get_memes"
|
||||
headers={'User-Agent':user_agent,}
|
||||
request=urllib.request.Request(url,None,headers)
|
||||
|
||||
with urllib.request.urlopen(request) as response, open('data.json', 'wb') as out_file:
|
||||
data = response.read()
|
||||
out_file.write(data)
|
||||
|
||||
with open('data.json') as data_file:
|
||||
jsonResponse = json.load(data_file)
|
||||
|
||||
jsonList = jsonResponse["data"]
|
||||
memeList = jsonList["memes"]
|
||||
memeData = random.choice(memeList)
|
||||
memeURL = memeData.get("url")
|
||||
print(memeURL)
|
||||
request=urllib.request.Request(memeURL,None,headers)
|
||||
|
||||
with urllib.request.urlopen(request) as response, open('image.jpg', 'wb') as out_file:
|
||||
data = response.read()
|
||||
out_file.write(data)
|
||||
|
||||
picStr = img2str('image.jpg')
|
||||
strList = picStr.split('\n')
|
||||
for i in strList:
|
||||
bot.say(i)
|
||||
18
modules/trump.py
Normal file
18
modules/trump.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import tweepy, random
|
||||
import sopel.module
|
||||
|
||||
@sopel.module.commands('trump')
|
||||
def trump(bot, trigger):
|
||||
consumer_key = "R3RIRZ9B6UxhdaHqYRPujbOBo"
|
||||
consumer_secret = "OXmfGjuINfGuGy0pPHlp0xIhPirRHO84I0J9ipGGh3F2UAx2gI"
|
||||
access_token = "553052831-cW2ph0YocqPeLGp9BOhoaShfUAp4gbsg1VAwOO2H"
|
||||
access_token_secret = "eAVMCB7P9WSIDk3j2UCqSSb0SVlELnGSkssJsTElfwCkL"
|
||||
|
||||
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
|
||||
auth.set_access_token(access_token, access_token_secret)
|
||||
|
||||
api = tweepy.API(auth)
|
||||
|
||||
tweets = api.user_timeline(screen_name = 'realDonaldTrump', count = 20, include_rts = False)
|
||||
tweet = random.choice(tweets)
|
||||
bot.say(tweet.text)
|
||||
Reference in New Issue
Block a user