I'm looking for the most efficient way to 'square' an image for use as an icon. For example, I've got a .png file whose dimensions are 24x20.I don't want to change the 'image' part of the image in any way, I just want to add transparent pixels to the edge of the image so it becomes 24x24. My research suggests that I need to create a transparent canvas 24x24, paste my image on to this, then save the result. I'm working in wxpython and was wondering if anyone could guide me through the process. Better yet, I also have PIL installed, and was wondering if there wasn't a built-in way of doing this. It seems like the kind of operation that would be carried out fairly regularly, but none of the imageops methods quite fit the bill.
Use image.paste to paste the image on a transparent background:
import Image
FNAME = '/tmp/test.png'
top = Image.open(FNAME).convert('RGBA')
new_w = new_h = max(top.size)
background = Image.new('RGBA', size = (new_w,new_h), color = (0, 0, 0, 0))
background.paste(top, (0, 0))
background.save('/tmp/result.png')
You could do it with numpy array pretty easy .. something like this
import matplotlib.pyplot as plt
import numpy as np
im1 = plt.imread('your_im.png')
im0 = np.zeros((24, 24, 4), dtype=im1.dtype)
im0[2:-2,:,:] = im1
plt.imsave('your_new_im.png', im0)
Here's a pure wxPython implementation.
import wx
app = wx.PySimpleApp()
# load input bitmap
bitmap = wx.Bitmap('input.png')
# compute dimensions
width, height = bitmap.GetSize()
size = max(width, height)
dx, dy = (size - width) / 2, (size - height) / 2
# create output bitmap
new_bitmap = wx.EmptyBitmap(size, size)
dc = wx.MemoryDC(new_bitmap)
dc.SetBackground(wx.Brush(wx.Colour(255, 0, 255)))
dc.Clear()
dc.DrawBitmap(bitmap, dx, dy)
del dc
# save output
image = wx.ImageFromBitmap(new_bitmap)
image.SetMaskColour(255, 0, 255)
image.SaveFile('output.png', wx.BITMAP_TYPE_PNG)
Related
I have a grayscale image and I want to create an alpha layer based on a range of pixel values. I want to know how can I create a fall-off function to generate such image.
The original image is the following:
I can use the color range in photoshop to select the shadows with fuzziness of 20%
And the resultant alpha channel is the following:
With fuzziness of 100%:
How can I generate such alpha channels in python with PIL?
I thought that maybe a subtract, but it does not generates a
The code to generate the image with Numpy and PIL:
from PIL import Image
import numpy as np
img = np.arange(0,256, 0.1).astype(np.uint8)
img = np.reshape(img, (img.shape[0], 1))
img = np.repeat((img), 500, axis=1)
img = Image.fromarray(img.T)
I tried to create a fall-off function from the distance of the pixel values but it does not have the same gradient. Maybe there is a different way?
def gauss_falloff(distance, c=0.2, alpha=255):
new_value = alpha * np.exp(-1 * ((distance) ** 2) / (c**2))
new_value = new_value.clip(0,255)
return new_value.astype(np.uint8)
test = img.T / 255
test = np.abs(test - pixel)
test = gauss_falloff(test, c=0.2, alpha=255)
test = Image.fromarray(test)
With my code:
Here's how you could do that
from PIL import Image, ImageDraw
# Create a new image with a transparent background
width, height = 300, 300
image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
# Create a drawing context for the image
draw = ImageDraw.Draw(image)
# Set the starting and ending colors for the gradient
start_color = (255, 0, 0)
end_color = (0, 0, 255)
# Draw a gradient line with the specified color range
for x in range(width):
color = tuple(int(start_color[i] + (end_color[i] - start_color[i]) * x / width)
for i in range(3))
draw.line((x, 0, x, height), fill=color)
# Save the image
image.save('gradient.png')
This code creates a new image with a transparent background and a drawing context for that image. Then it draws a gradient line on the image with the specified color range. Finally, it saves the image as a PNG file.
Note: The Python Imaging Library (PIL) has been replaced by the Pillow library, which is a fork of PIL. If you are using Pillow, you can use the same code as above, but you need to import the Image and ImageDraw modules from the Pillow package instead of the PIL package.
I'm making a GUI toolkit for the Python Arcade library, but I am stuck on a problem. I want the user to be able to customize sizes for the GUI widgets and graphics in pixels (width, height). But currently, the graphics are images. I have the images, but I want the user to be able to customize their sizing.
One of the images is shown below. Instead of using PIL to just stretch the width and height of the image, I need something else. Just stretching the width and height will make the border look too thick.
Is there an easy way to cut certain parts of the image to enable easy use for extending it? Borders would look like this. They would be split to extend the image. Some of the parts can be stretched, but some can not.
Your example seems to use a simple style, so a simplified solution could be used for it as well.
from PIL import Image
def resizeImage(im, corner, new_size):
'''
corner_size and new_size are 2-element tuples of xy sizes for the corner size and target size.
'''
# Get corners from image
tl = im.crop(0, 0, corner[0], corner[1])
tr = im.crop(im.size[0] - corner[0], 0, size[0], corner[1])
bl = im.crop(0, im.size[1] - corner[1], corner[0], size[1])
br = im.crop(im.size[0] - corner[0], im.size[1] - corner[1], size[0], size[1])
# Get 1-pixel slices of midsections, then scale them up as needed
h_slice = im.crop(corner[0] + 1, 0, corner[0] + 2, im.size[1])
h_slice = h_slice.resize((new_size[0] - 2 * corner[0], im.size[1]))
v_slice = im.crop(0, corner[1] + 1, im.size[0], corner[1] + 2)
v_slice = v_slice.resize((im.size[0], new_size[1] - 2 * corner[1]))
# create new image
new_im = Image.new('RGBA', new_size)
# paste on segments and corners
new_im.paste(tl, (0, 0))
new_im.paste(tr, (new_size[0] - corner[0], 0))
new_im.paste(tl, (0, new_size[1] - corner[1]))
new_im.paste(tl, (new_size[0] - corner[0], new_size[1] - corner[1]))
return im
This answer assumes that your borders are completely homogenous, in that there's no difference between any slice of the border (no patterns/textures).
If you do want to account for this, you can check out RenPy's approach to the problem. I'd track down the source code too, but the solution I proposed is a minimal solution for your specific example with a simple GUI style.
(Note that I have not run this code, so there may be a 1-pixel offset somewhere that I could have missed.)
It seems to be no easy way for resizing ( liquid resizing doesn't work here ) except (as suggested in the question with the second image) dividing the image using PIL crop() into nine (9) sub-images and resize them separately (except the corner sub-images, which won't become resized). The resized parts are then put together in a new image with the requested new size by pasting them using PIL paste() onto it. The borders are stretched only along their length and not along their thickness. Here how it looks like if the original image becomes resized with the further down provided resizeExceptBorder() function:
Original image (200 x 30)
new_img_1 = resizeExceptBorder(PIL_image,(300,90),(5,5,5,5))
Resized image (300 x 90)
new_img_2 = resizeExceptBorder(PIL_image,(400,150),(5,5,5,5))
Resized (400 x 150)
And here the code of the function I have put together for this purpose:
def resizeExceptBorder(PIL_image, newSize, borderWidths):
"""
newSize = (new_width, new_height)
borderWidths = (leftWidth, rightWidth, topWidth, bottomWidth)"""
pl_img = PIL_image
sXr, sYr = newSize # ( 800, 120 ) # resized size X, Y
lWx, rWx , tWy, bWy = borderWidths
sX, sY = pl_img.size
sXi, sYi = sXr-(lWx+rWx), sYr-(tWy+bWy)
pl_lft_top = pl_img.crop(( 0, 0, lWx, tWy))
pl_rgt_top = pl_img.crop((sX-rWx, 0, sX, tWy))
pl_lft_btm = pl_img.crop(( 0, sY-bWy, lWx, sY))
pl_rgt_btm = pl_img.crop((sX-rWx, sY-bWy, sX, sY))
# ---
pl_lft_lft = pl_img.crop(( 0, tWy, lWx,sY-bWy)).resize((lWx ,sYi))
pl_rgt_rgt = pl_img.crop((sX-rWx, tWy, sX,sY-bWy)).resize((rWx ,sYi))
pl_top_top = pl_img.crop(( lWx, 0, sX-rWx, tWy)).resize((sXi ,tWy))
pl_btm_btm = pl_img.crop(( lWx, sY-bWy, sX-rWx, sY)).resize((sXi ,bWy))
# ---
pl_mid_mid = pl_img.crop(( lWx, tWy, sX-rWx,sY-bWy)).resize((sXi,sYi))
# -------
pl_new=Image.new(pl_img.mode, (sXr, sYr))
# ---
pl_new.paste(pl_mid_mid, ( lWx, tWy))
# ---
pl_new.paste(pl_top_top, ( lWx, 0))
pl_new.paste(pl_btm_btm, ( lWx,sYr-bWy))
pl_new.paste(pl_lft_lft, ( 0, tWy))
pl_new.paste(pl_rgt_rgt, (sXr-rWx, tWy))
# ---
pl_new.paste(pl_lft_top, ( 0, 0))
pl_new.paste(pl_rgt_top, (sXr-rWx, 0))
pl_new.paste(pl_lft_btm, ( 0,sYr-bWy))
pl_new.paste(pl_rgt_btm, (sXr-rWx,sYr-bWy))
# ---
return pl_new
#:def
The corner coordinates of square_1 = (0, 0, 1920, 1080). I then define square_2 as a smaller ROI within square one using numpy slicing like so roi = square_1[y1:y2, x1:x2]. I then resize square_1 using square_resize = cv2.resize(square_1, (960, 540), interpolation = cv2.INTER_AREA) . However, now my ROI is no longer accurate. I have a tool which tells me the screen coords of the mouse pos, which is how I find the dimensions of the ROI, but I need a function that translates the ROI coordinates I find, given the coordinates of square_1, in terms of the coordinates of square_resize.
EDIT:
Solved using Panda50's answer. grab_screen() is my own custom function for getting screenshots. Here is my code if it helps anyone. It does not give 100% accurate coords but you can play around some and narrow it down.
from cv2 import cv2
import numpy as np
y1 = int(92 / 2)
y2 = int(491 / 2)
x1 = int(233 / 2)
x2 = int(858 / 2)
# grab screen and convert to RGB
screen = grab_screen(region = (0, 0, 1920, 1080))
screen = cv2.cvtColor(screen, cv2.COLOR_BGR2RGB)
# resize screen
screen = cv2.resize(screen, (960, 540), interpolation = cv2.INTER_AREA)
# define ROI
roi = screen[y1:y2, x1:x2].copy()
cv2.imshow('roi', roi)
cv2.waitKey()
cv2.destroyAllWindows()
In python, = associate one variable with another. By changing square_1 you'll also change roi .
You have to use :
roi = square_1[y1:y2, x1:x2].copy()
I can't work out how to overlay a gizeh animation onto a video so that the vector graphics are visible but the background is transparent so the video is visible underneath the animation. I've tried lots of different ways and nothing seems to work. All I ever get is the gizeh animation completely hiding the underlying video.
This was my latest effort, just simply trying to draw a red line over the video, I've tried using the mask_color vfx method to create a mask that uses the Surface bg_color, but it doesn't have any effect.
import gizeh
from moviepy.editor import *
def make_frame(t):
surface = gizeh.Surface(width=720, height=1280, bg_color=(0.5, 0.5, 0))
line = gizeh.polyline(points=[(0, 1180), (720, 1180)], stroke_width=3, stroke=(1, 0, 0))
line.draw(surface)
return surface.get_npimage()
original_clip = VideoFileClip("test_original_video.mp4")
graphics_clip = VideoClip(make_frame, duration=original_clip.duration)
masked_graphics_clip = vfx.mask_color(graphics_clip, [0.5, 0.5, 0])
final_clip = CompositeVideoClip(
[original_clip,
graphics_clip],
size=(720, 1280))
final_clip.write_videofile("test_output_video.mp4", fps=30))
How do I define and apply the mask of the animated graphics clip?
Zulko, the author of moviepy and gizeh very kindly helped me find a solution to this (full details here https://github.com/Zulko/moviepy/issues/898).
The trick is to:
Use the same make_frame function for both the graphics and the animation.
Return the numpy image array with the transparent=True option, which returns an opacity value for each pixel after the RGB values [so the shape of the array is (width, height, 4)]
For the mask clip, slice the array so it only uses the opacity value [giving a shape of (width, height, 1)]
For the graphics clip, slice the array so it only use the RGB values [giving a shape of (width, height, 3)]
Apply the mask clip to the graphics clip
The working code looks like this:
import gizeh
from moviepy.editor import *
def make_frame(t):
surface = gizeh.Surface(width=720, height=1280)
line = gizeh.polyline(points=[(0, 1180), (720, 1180)], stroke_width=10, stroke=(1, 0, 0))
line.draw(surface)
return surface.get_npimage(transparent=True)
original_clip = VideoFileClip("test_original_video.mp4")
graphics_clip_mask = VideoClip(lambda t: make_frame(t)[:, :, 3] / 255.0,
duration=original_clip.duration, ismask=True)
graphics_clip = VideoClip(lambda t: make_frame(t)[:, :, :3],
duration=original_clip.duration).set_mask(graphics_clip_mask)
final_clip = CompositeVideoClip(
[original_clip,
graphics_clip],
size=(720, 1280)
)
final_clip.write_videofile("test_output_video.mp4", fps=30)
Using Python I want to be able to draw text at different angles using PIL.
For example, imagine you were drawing the number around the face of a clock. The number 3 would appear as expected whereas 12 would we drawn rotated counter-clockwise 90 degrees.
Therefore, I need to be able to draw many different strings at many different angles.
Draw text into a temporary blank image, rotate that, then paste that onto the original image. You could wrap up the steps in a function. Good luck figuring out the exact coordinates to use - my cold-fogged brain isn't up to it right now.
This demo writes yellow text on a slant over an image:
# Demo to add rotated text to an image using PIL
import Image
import ImageFont, ImageDraw, ImageOps
im=Image.open("stormy100.jpg")
f = ImageFont.load_default()
txt=Image.new('L', (500,50))
d = ImageDraw.Draw(txt)
d.text( (0, 0), "Someplace Near Boulder", font=f, fill=255)
w=txt.rotate(17.5, expand=1)
im.paste( ImageOps.colorize(w, (0,0,0), (255,255,84)), (242,60), w)
It's also usefull to know our text's size in pixels before we create an Image object. I used such code when drawing graphs. Then I got no problems e.g. with alignment of data labels (the image is exactly as big as the text).
(...)
img_main = Image.new("RGB", (200, 200))
font = ImageFont.load_default()
# Text to be rotated...
rotate_text = u'This text should be rotated.'
# Image for text to be rotated
img_txt = Image.new('L', font.getsize(rotate_text))
draw_txt = ImageDraw.Draw(img_txt)
draw_txt.text((0,0), rotate_text, font=font, fill=255)
t = img_value_axis.rotate(90, expand=1)
The rest of joining the two images together is already described on this page.
When you rotate by an "unregular" angle, you have to improve this code a little bit. It actually works for 90, 180, 270...
Here is a working version, inspired by the answer, but it works without opening or saving images.
The two images have colored background and alpha channel different from zero to show what's going on. Changing the two alpha channels from 92 to 0 will make them completely transparent.
from PIL import Image, ImageFont, ImageDraw
text = 'TEST'
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', 50)
width, height = font.getsize(text)
image1 = Image.new('RGBA', (200, 150), (0, 128, 0, 92))
draw1 = ImageDraw.Draw(image1)
draw1.text((0, 0), text=text, font=font, fill=(255, 128, 0))
image2 = Image.new('RGBA', (width, height), (0, 0, 128, 92))
draw2 = ImageDraw.Draw(image2)
draw2.text((0, 0), text=text, font=font, fill=(0, 255, 128))
image2 = image2.rotate(30, expand=1)
px, py = 10, 10
sx, sy = image2.size
image1.paste(image2, (px, py, px + sx, py + sy), image2)
image1.show()
The previous answers draw into a new image, rotate it, and draw it back into the source image. This leaves text artifacts. We don't want that.
Here is a version that instead crops the area of the source image that will be drawn onto, rotates it, draws into that, and rotates it back. This means that we draw onto the final surface immediately, without having to resort to masks.
def draw_text_90_into (text: str, into, at):
# Measure the text area
font = ImageFont.truetype (r'C:\Windows\Fonts\Arial.ttf', 16)
wi, hi = font.getsize (text)
# Copy the relevant area from the source image
img = into.crop ((at[0], at[1], at[0] + hi, at[1] + wi))
# Rotate it backwards
img = img.rotate (270, expand = 1)
# Print into the rotated area
d = ImageDraw.Draw (img)
d.text ((0, 0), text, font = font, fill = (0, 0, 0))
# Rotate it forward again
img = img.rotate (90, expand = 1)
# Insert it back into the source image
# Note that we don't need a mask
into.paste (img, at)
Supporting other angles, colors etc is trivial to add.
Here's a fuller example of watermarking diagonally. Handles arbitrary image ratios, sizes and text lengths by calculating the angle of the diagonal and font size.
from PIL import Image, ImageFont, ImageDraw
import math
# sample dimensions
pdf_width = 1000
pdf_height = 1500
#text_to_be_rotated = 'Harry Moreno'
text_to_be_rotated = 'Harry Moreno (morenoh149#gmail.com)'
message_length = len(text_to_be_rotated)
# load font (tweak ratio based on your particular font)
FONT_RATIO = 1.5
DIAGONAL_PERCENTAGE = .5
diagonal_length = int(math.sqrt((pdf_width**2) + (pdf_height**2)))
diagonal_to_use = diagonal_length * DIAGONAL_PERCENTAGE
font_size = int(diagonal_to_use / (message_length / FONT_RATIO))
font = ImageFont.truetype(r'./venv/lib/python3.7/site-packages/reportlab/fonts/Vera.ttf', font_size)
#font = ImageFont.load_default() # fallback
# target
image = Image.new('RGBA', (pdf_width, pdf_height), (0, 128, 0, 92))
# watermark
opacity = int(256 * .5)
mark_width, mark_height = font.getsize(text_to_be_rotated)
watermark = Image.new('RGBA', (mark_width, mark_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)
draw.text((0, 0), text=text_to_be_rotated, font=font, fill=(0, 0, 0, opacity))
angle = math.degrees(math.atan(pdf_height/pdf_width))
watermark = watermark.rotate(angle, expand=1)
# merge
wx, wy = watermark.size
px = int((pdf_width - wx)/2)
py = int((pdf_height - wy)/2)
image.paste(watermark, (px, py, px + wx, py + wy), watermark)
image.show()
Here it is in a colab https://colab.research.google.com/drive/1ERl7PiX6xKy5H9EEMulBKPgglF6euCNA?usp=sharing you should provide an example image to the colab.
I'm not saying this is going to be easy, or that this solution will necessarily be perfect for you, but look at the documentation here:
http://effbot.org/imagingbook/pil-index.htm
and especially pay attention to the Image, ImageDraw, and ImageFont modules.
Here's an example to help you out:
import Image
im = Image.new("RGB", (100, 100))
import ImageDraw
draw = ImageDraw.Draw(im)
draw.text((50, 50), "hey")
im.rotate(45).show()
To do what you really want you may need to make a bunch of separate correctly rotated text images and then compose them all together with some more fancy manipulation. And after all that it still may not look great. I'm not sure how antialiasing and such is handled for instance, but it might not be good. Good luck, and if anyone has an easier way, I'd be interested to know as well.
If you a using aggdraw, you can use settransform() to rotate the text. It's a bit undocumented, since effbot.org is offline.
# Matrix operations
def translate(x, y):
return np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])
def rotate(angle):
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
def draw_text(image, text, font, x, y, angle):
"""Draw text at x,y and rotated angle radians on the given PIL image"""
m = np.matmul(translate(x, y), rotate(angle))
transform = [m[0][0], m[0][1], m[0][2], m[1][0], m[1][1], m[1][2]]
draw = aggdraw.Draw(image)
draw.settransform(transform)
draw.text((tx, ty), text, font)
draw.settransform()
draw.flush()