Pillow rotate not quite right - python

It is hard to explain so please look at the image. It appears to rotate the data within the image but not the image itself.
original:
Rotated -90:
block = scene.read_block()
image = PILImage.fromarray(block)
image_rotate = image.rotate(-90)
buf = BytesIO()
image_rotate.save(buf, "JPEG", quality=90)
return buf.getvalue()
The viewport didn't rotate only the image inside.
Thanks

you need to use expand=True to change the size
image.rotate(-90, expand=True)
it is in the documentation

Related

I get an 'AttributeError: shape' when trying to rotate an image with OpenCV

I am trying to import, resize, and conditionally rotate an image with OpenCV but I'm running into some trouble. To bring in the image and resize it I use:
def draw_plane(self):
# Import image for plane
plane_path = 'planes/' + self.plane + '.jpg'
plane = Image.open(plane_path)
# Get image size
bg_height = plane.size[1]
bg_width = plane.size[0]
# Resize and crop image
if bg_height > bg_width:
# Resize
ratio = bg_width/bg_height
img_width = self.p_width
img_height = int(self.p_height/ratio)
plane_resized = plane.resize((img_width,img_height))
# Crop
top = int((img_height-self.p_height)/2)
bottom = int(((img_height-self.p_height)/2)+self.p_height)
plane_cropped = plane_resized.crop((0,top,self.p_width,bottom))
print('top:',top,'\nbottom:',bottom)
self.plane_img = plane_cropped
if bg_height < bg_width:
# Resize
ratio = bg_height/bg_width
img_width = int(self.p_width/ratio)
img_height = self.p_height
plane_resized = plane.resize((img_width,img_height))
# Crop
left = int((img_width-self.p_width)/2)
right = int(((img_width-self.p_width)/2)+self.p_width)
plane_cropped = plane_resized.crop((left,0,right,self.p_height))
self.plane_img = plane_cropped
else:
pass
If the name of an image being used as a frame for plane is in a list I call the following method and if the first item in a list of attributes for the final composition is "Polaroid" I want it to rotate plane.
def adjust_plane(self):
if a.attr[0] == 'polaroid':
plane = self.plane_img
height, width = plane.shape[:2] <----
center = (width/2, height/2)
rotate_matrix = cv2.getRotationMatrix2D(center=center, angle=-30, scale=1)
rotated_plane = cv2.warpAffine(plane, rotate_matrix, (width, height))
self.plane_img = rotated_plane
But when I run the code I get: "AttributeError: shape" on the line I noted in the code block. This is all taking place in the same class, including the conditional that triggers adjust_plane().
I admit that I am at a point in learning to program that I am just beginning to wrap my head around objects as a concept. Is there maybe some issue that this is no longer an image but is an "image object", if there is such a thing? Any help is appreciated, I've been chewing on this error for far too long.
It appears that the issue is that OpenCV and PIL images don't play well together. Someone else could better explain exactly why.
I brought in OpenCV to rotate the image because I thought PIL could not rotate an image by specific degree rather than just 90° steps, but I was wrong about that. The code below accomplishes what I wanted with the PIL library and I was able to do away with OpenCV.
plane.rotate(30, Image.NEAREST, expand = 1)

universal way to crop animated GIF?

I am working on a solution to crop GIF. I got it working but the size of the image increases a lot. IE, it was 500KB animated GIF but after cropping it's 8MB animated GIF.
I suspect that's because I transform it to RGB, then merge frame with previous one if GIF has a partial mode.
here is an example of how I do that:
img = Image.open(file_path)
last_frame = img.convert('RGBA')
p = img.getpalette()
# this method analyzes image and determines if it's in partial mode.
mode = analyseImage(img)['mode']
all_frames = []
frames = ImageSequence.Iterator(img)
for frame in frames:
if not frame.getpalette():
frame.putpalette(p)
new_frame = Image.new('RGBA', img.size)
if mode == 'partial':
new_frame.paste(last_frame)
new_frame.paste(frame, (0, 0), frame.convert('RGBA'))
last_frame = new_frame.copy()
new_frame.thumbnail(size, Image.ANTIALIAS)
all_frames.append(new_frame)
return all_frames
and then I store it as new image using method:
new_image_bytes = BytesIO()
om = all_frames[0]
om.info = img.info
om.save(new_image_bytes, format='gif', optimize=True, save_all=True, append_images=all_frames[1:], duration=img.info.get('duration'), loop=img.info.get('loop'))
and this image is 8MB instead of 500KB
Do I miss anything obvious ?
Basically, what you do here is discard all compression optimizations that the original GIF might`ve had.
Let`s say there is a 2-frame GIF, the second frame of which only changes pixels outside the crop window. After being cropped it could`ve become empty (and thus small in size), but your process makes it a full copy of the first frame, ultimately leading to the bloat you describe.

Pillow - Transparency over non-transparent image with paste

Let me prefix with a disclaimer that I am clueless when it comes to imaging/graphics all together, so maybe I'm lacking a fundamental understanding with something here.
I'm trying to paste an image (game_image) to my base image (image) with a transparent overlay (overlay_image) over top to add some darkening for the text.
Here's an example of the expected result:
Here's an example of what my current code generates:
Here is my current code:
from PIL import Image, ImageFont, ImageDraw
# base image sizing specific to Twitter recommended
base_image_size = (1600, 900)
base_image_mode = "RGBA"
base_image_background_color = (0, 52, 66)
image = Image.new(base_image_mode, base_image_size, base_image_background_color)
# game_image is the box art image on the left side of the card
game_image = Image.open("hunt.jpg")
image.paste(game_image)
# overlay_image is the darkened overlay over the left side of the card
overlay_image = Image.new(base_image_mode, base_image_size, (0, 0, 0))
overlay_image.putalpha(128)
# x position should be negative 50% of base canvas size
image.paste(overlay_image, (-800, 0), overlay_image)
image.save("test_image.png", format="PNG")
You can see that the game image sort of inherits the transparency from the overlay. I suspect it has something to do with the mask added in my paste above, but I tried looking into what masking is & its just beyond my understanding in any context I find it in.
Any help on understanding why this occurs and/or how I can resolve is appreciated!
You are super close... All you need, is to use Image.alpha_composite instead of paste. So, the last two lines of your code should be:
image = Image.alpha_composite(image, overlay_image)
image.save("test_image.png", format="PNG")

How can i find cycles in a skeleton image with python libraries?

I have many skeletonized images like this:
How can i detect a cycle, a loop in the skeleton?
Are there "special" functions that do this or should I implement it as a graph?
In case there is only the graph option, can the python graph library NetworkX can help me?
You can exploit the topology of the skeleton. A cycle will have no holes, so we can use scipy.ndimage to find any holes and compare. This isn't the fastest method, but it's extremely easy to code.
import scipy.misc, scipy.ndimage
# Read the image
img = scipy.misc.imread("Skel.png")
# Retain only the skeleton
img[img!=255] = 0
img = img.astype(bool)
# Fill the holes
img2 = scipy.ndimage.binary_fill_holes(img)
# Compare the two, an image without cycles will have no holes
print "Cycles in image: ", ~(img == img2).all()
# As a test break the cycles
img3 = img.copy()
img3[0:200, 0:200] = 0
img4 = scipy.ndimage.binary_fill_holes(img3)
# Compare the two, an image without cycles will have no holes
print "Cycles in image: ", ~(img3 == img4).all()
I've used your "B" picture as an example. The first two images are the original and the filled version which detects a cycle. In the second version, I've broken the cycle and nothing gets filled, thus the two images are the same.
First, let's build an image of the letter B with PIL:
import Image, ImageDraw, ImageFont
image = Image.new("RGBA", (600,150), (255,255,255))
draw = ImageDraw.Draw(image)
fontsize = 150
font = ImageFont.truetype("/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf", fontsize)
txt = 'B'
draw.text((30, 5), txt, (0,0,0), font=font)
img = image.resize((188,45), Image.ANTIALIAS)
print type(img)
plt.imshow(img)
you may find a better way to do that, particularly with path to the fonts. Ii would be better to load an image instead of generating it. Anyway, we have now something to work on:
Now, the real part:
import mahotas as mh
img = np.array(img)
im = img[:,0:50,0]
im = im < 128
skel = mh.thin(im)
noholes = mh.morph.close_holes(skel)
plt.subplot(311)
plt.imshow(im)
plt.subplot(312)
plt.imshow(skel)
plt.subplot(313)
cskel = np.logical_not(skel)
choles = np.logical_not(noholes)
holes = np.logical_and(cskel,noholes)
lab, n = mh.label(holes)
print 'B has %s holes'% str(n)
plt.imshow(lab)
And we have in the console (ipython):
B has 2 holes
Converting your skeleton image to a graph representation is not trivial, and I don't know of any tools to do that for you.
One way to do it in the bitmap would be to use a flood fill, like the paint bucket in photoshop. If you start a flood fill of the image, the entire background will get filled if there are no cycles. If the fill doesn't get the entire image then you've found a cycle. Robustly finding all the cycles could require filling multiple times.
This is likely to be very slow to execute, but probably much faster to code than a technique where you trace the skeleton into graph data structure.

How do I modify my Python image resizer code to do this?

When I resize a picture that is smaller than desired, I want the image to NOT get resized...but instead be in the center, with white padding around it.
So, let's say I have an icon that's 50x50. But I want to resize it to 100x100. If I pass it to this function, I would like it to return me the same icon that is centered, with white padding 25 pixels on each side.
Of course, I'd like this to work with images of any width/height, and not just a square like the example above.
My current code is below.
I don't know why I did if height=None: height=width...I think it was just because it worked when I did it before.
def create_thumbnail(f, width=200, height=None):
if height==None: height=width
im = Image.open(StringIO(f))
imagex = int(im.size[0])
imagey = int(im.size[1])
if imagex < width or imagey < height:
return None
if im.mode not in ('L', 'RGB', 'RGBA'):
im = im.convert('RGB')
im.thumbnail((width, height), Image.ANTIALIAS)
thumbnail_file = StringIO()
im.save(thumbnail_file, 'JPEG')
thumbnail_file.seek(0)
return thumbnail_file
This code may have many bugs in it, not sure.
One option is to create a blank white image of the size you want, then draw the existing image in the center.

Categories

Resources