Across most of the Internet, our identities are squashed into a little profile photo and display name, and these are the miniature canvas upon which we can express our personalities or make a statement. Maybe to take part of a social movement, represent an organization or brand; or just to announce that we're "open to work" or feeling the Halloween spirit π»
Curious about how to build one of those image generators for people to liven up their profile photos? Here is a simple version in Python that uses a partially transparent image overlay for a custom frame and stickers.
This takes a user's profile photo (from Twitter), adds the overlay image on top, and then you can grab the resulting image as a data url to render in HTML or write it as a (temporary) file (which can also be passed back to Twitter to update the user's profile photo).
Inspiration for this post: We recently built this feature as a fun little project for Block Party. Interested in working with us? We're hiring!
First, let's fetch the user's profile photo from Twitter. I've hand-waved the piece about the Twitter client (sorry) but you can use something like Tweepy.
from PIL import Image
import requests
PFP_IMAGE_SIZE = (400, 400) # max/standard size on Twitter
def get_original_pfp_image(twitter_screen_name):
# TODO(you) Implement this
client = create_twitter_client_for_app()
twitter_user_json = client.get_twitter_user_data_by_screen_name(
twitter_screen_name)
pfp_url = twitter_user_json.get("profile_image_url_https", "")
pfp_original_url = pfp_url.replace("_normal", "")
pfp_image = Image.open(
requests.get(pfp_original_url, stream=True).raw)
# We aren't guaranteed that the user's profile image is
# the right size, which will cause issues when trying to
# apply the overlay.
if pfp_image.size != PFP_IMAGE_SIZE:
pfp_image = pfp_image.resize(
PFP_IMAGE_SIZE, Image.ANTIALIAS)
return pfp_image
If you want to simplify for development and testing, you can pass in a local image path instead.
from PIL import Image
PFP_IMAGE_SIZE = (400, 400)
# Note that this has a slightly different function signature
# than the alternative sample code.
def get_original_pfp_image(pfp_image_path):
pfp_image = Image.open(pfp_image_path)
if pfp_image.size != PFP_IMAGE_SIZE:
pfp_image = pfp_image.resize(
PFP_IMAGE_SIZE, Image.ANTIALIAS)
return pfp_image
Next, let's load the overlay image, composite it with the original profile photo, and create the new overlaid image.
The overlay image here should be a 400x400 PNG with transparency.
import PIL
from PIL import Image
def get_overlay_image(overlay_image_path):
overlay_image = Image.open(overlay_image_path)
return overlay_image
def generate_overlaid_image(twitter_screen_name):
pfp_image = get_original_pfp_image(twitter_screen_name)
if not pfp_image:
return None
# We need RGBA mode for transparency in the image
pfp_image_rgba = pfp_image.convert(mode="RGBA")
# TODO(you) Replace with your overlay image path
overlay_image = get_overlay_image("overlay.png")
overlaid_pfp_image = PIL.Image.alpha_composite(
pfp_image_rgba, overlay_image)
return overlaid_pfp_image
Now let's get the image as a data url so we can render it in HTML!
import base64
from io import BytesIO
DATA_IMAGE_PREFIX = "data:image/png;base64,"
def generate_overlaid_pfp_image_data_url(twitter_screen_name):
overlaid_pfp_image = generate_overlaid_pfp_image(
twitter_screen_name)
if not overlaid_pfp_image:
return None
image_io = BytesIO()
overlaid_pfp_image.save(image_io, "png", quality=95)
data_url = DATA_IMAGE_PREFIX + base64.b64encode(
image_io.getvalue()).decode("ascii")
return data_url
To preview in Jinja -- for example, at all the different sizes that Twitter uses -- we just use this image data url as the src
of an img
element.
{% for dims in [(24, 24), (48, 48), (73, 73), (400, 400)] %}
<img src="{{ image_data_url }}"
width="{{ dims[0] }}"
height="{{ dims[1] }}" />
{% endfor %}
Lastly, if you do want to set the user's profile photo back on Twitter, you can create a temporary file and send it through the API.
import base64
from io import BytesIO
DATA_IMAGE_PREFIX = "data:image/png;base64,"
def update_user_twitter_pfp(twitter_user_id, image_data_url):
image_data = image_data_url[len(DATA_IMAGE_PREFIX):]
buffer = BytesIO(base64.b64decode(image_data))
image_tempfile = tempfile.NamedTemporaryFile(suffix=".png")
try:
image_tempfile.write(buffer.read())
image_tempfile.flush()
# This client is created with the user's OAuth
# credentials (vs. app credentials) so we can
# set their profile photo.
client = create_twitter_client_for_user(
twitter_user_id)
client.update_twitter_user_profile_image(
image_tempfile.name)
finally:
buffer.close()
image_tempfile.close()
Here's how it looks!
Top comments (1)
This is neat :)