How to resize an image with PIL getbbox to the maximum possible size? - image

I'm trying to resize sprites to a 96x96 boundary while retaining the aspect ratio.
The following code:
im.getbbox()
Returns a tuple containing the bounds of the sprite (original backgrounds are transparent), and I am stuck on this next part - I want to take that part of the image and resize it as large as it can possibly go within a 96x96 boundary
Here's an example of some sprites from Pokemon:
Since some are 80x80, some are 64x64, and the largest are 96x96, I would like to effectively select the contents of the sprite with im.getbbox() and then enlarge it to fit on top of a 96px white background.
Could anyone please help? I'm not sure how to maximise it within the bounds
My current code is as follows:
x = 0
for dirname, dirs, files in os.walk(path):
for filename in files:
x+=1
path = dirname + "/" + filename
print(path)
im = Image.open(path)
fill_color = (255,255,255)
im = im.convert("RGBA")
if im.mode in ('RGBA', 'LA'):
background = Image.new(im.mode[:-1], im.size, fill_color)
background.paste(im, im.split()[-1])
im = background
imResize = ImageOps.fit(im, (96, 96), Image.BOX, 0, (0.5, 0.5))
imResize.save("dataset/images/" + str(x) + '.png', 'PNG')
It takes the image and pastes it on to a white background and sets the size as 96px. This is fine for the native 96px images but the aspect ratio of the smaller images is ruined. By being able to enlarge them to the maximum bounds of a 96px image then it should prevent this from happening
Thanks!

ok, made my own pics, here:
zY28f_64.png, zY28f_80.png , zY28f_96.png
ended up with code :
from PIL import Image, ImageOps
import os
path= 'images'
x = 0
for dirname, dirs, files in os.walk(path):
for filename in files:
x+=1
path = dirname + "/" + filename
print(path)
im = Image.open(path)
im.show()
fill_color = (255,255,255)
im = im.convert("RGBA")
if im.mode in ('RGBA', 'LA'):
background = Image.new(im.mode[:-1], im.size, fill_color)
# background.paste(im, im.split()[-1])
background.paste(im)
im = background
im_inv = ImageOps.invert(im) ### make white background black
imResize = ImageOps.fit(im.crop(im_inv.getbbox()), (96, 96), Image.BOX, 0, (0.5, 0.5)) # im.crop crop on box got from image with black background, Image.getbbox works only with black bacground returning biggest box containing non zero(non black ) pixels
imResize.show()
# imResize.save("dataset/images/" + str(x) + '.png', 'PNG')
output:
zY28f_64.png2.png , zY28f_80.png1.png , zY28f_96.png3.png
main changes here:
make white background black
im_inv = ImageOps.invert(im)
im.crop crop on box got from image with black background, Image.getbbox works only with black background returning biggest box containing non zero(non black ) pixels
imResize = ImageOps.fit(im.crop(im_inv.getbbox()), (96, 96), Image.BOX, 0, (0.5, 0.5))
Since ImageOps.fit(...) line return chopped images, not sure how it works, to keep the entire figures I used code from PIL Image.resize() not resizing the picture to get:
from PIL import Image, ImageOps
import os
import math
path= 'images'
x = 0
for dirname, dirs, files in os.walk(path):
for filename in files:
x+=1
path = dirname + "/" + filename
print(path)
im = Image.open(path)
fill_color = (255,255,255)
im = im.convert("RGBA")
if im.mode in ('RGBA', 'LA'):
background = Image.new(im.mode[:-1], im.size, fill_color)
# background.paste(im, im.split()[-1])
background.paste(im)
im = background
im_inv = ImageOps.invert(im)
imResize = im.crop(im_inv.getbbox())
width, height = imResize.size
print(width, height)
if height > width:
ratio = math.floor(height / width)
newheight = ratio * 96
print(imResize, ratio, newheight)
imResize = imResize.resize((96, newheight))
else:
ratio = math.floor(width /height )
newwidth = ratio * 96
print(imResize, ratio, newwidth)
imResize = imResize.resize((newwidth, 96))
imResize.show()
print(imResize.size)
imResize.save(path + str(x) + '.png', 'PNG')
output:
zY28f_64.png2.png , zY28f_80.png1.png , zY28f_96.png3.png

Related

Problem with images overlapping in pygame

Im having problems with blitting images to rect objects in pygame. i have a background image blitted to my main pygame window, and also an image blitted to a rect object on the screen which moves. the problem i am having is the rect object is overlapping my background image when its moving around. i was looking to only be able to see the green helicopter shape and not the black outline around it. sorry if i havent explained this very well. will try to include all files im using.
Thanks for any help
import pygame as pg
import random as r
import time
pg.init()
MAX_X = 1190
MAX_Y = 590
MIN_X = 10
MIN_Y = 10
SIZE = 100
SPEED = 1
COLOR = (0,255,0)
move_amount = 0
wn = pg.display.set_mode((1200, 600))
BG_IMG = pg.image.load('bg.png').convert()
BG_IMG = pg.transform.scale(BG_IMG, (1200, 600))
class Wall (pg.Rect):
def __init__(self, posX, posY):
self.xcor = posX
self.ycor = posY
self.rect = None
class Heli (pg.Rect):
def __init__(self, posX, posY):
self.image = pg.image.load('art.png').convert()
self.rect = self.image.get_rect()
self.xcor = posX
self.ycor = posY
# top and bottom constant walls
TOP = pg.Rect(MIN_X, MIN_Y, MAX_X, 3)
BOTTOM = pg.Rect(MIN_X, MAX_Y, MAX_X, 3)
heli = Heli(MIN_X, MAX_Y //2)
# keep moving walls in a list
moving_walls = [Wall(MAX_X, r.randint((MIN_Y + 10), (MAX_Y - 10)))]
# main loop
while True:
# fill screen
wn.fill('black')
# editing objects to move
# blitting must happen before everything else
pg.draw.rect(wn,COLOR, heli.rect)
wn.blit(BG_IMG, (0,0))
wn.blit(heli.image, heli.rect)
heli.rect.y += move_amount
heli.rect.y += 1
# use a variable to control how much movement is happening
# movement happens continuosly
# if key down it oves if key up it doesnt
for wall in moving_walls :
wall.rect = pg.Rect(wall.xcor, wall.ycor, 3, SIZE)
pg.draw.rect(wn, COLOR, wall.rect)
wall.xcor -= SPEED
if wall.xcor < MIN_X + 10:
wall.xcor = MAX_X
wall.ycor = r.randint((MIN_Y), (MAX_Y - SIZE))
# drawing all objects back to the screen
pg.draw.rect(wn, COLOR, TOP)
pg.draw.rect(wn, COLOR, BOTTOM)
# update window
pg.display.update()
# event handling
for ev in pg.event.get():
if ev.type == pg.KEYDOWN:
if ev.key == pg.K_UP:
move_amount = -3
if ev.type == pg.KEYUP:
move_amount = 0
if ev.type == pg.QUIT:
pg.quit()
time.sleep(0.01)
You discard the transparency information of the image. You have to use convert_alpha instead of convert:
self.image = pg.image.load('art.png').convert()
self.image = pg.image.load('art.png').convert_alpha()
The pygame documentation notes that:
The returned Surface will contain the same color format, colorkey and alpha transparency as the file it came from. You will often want to call convert() with no arguments, to create a copy that will draw more quickly on the screen.
For alpha transparency, like in .png images, use the convert_alpha() method after loading so that the image has per pixel transparency.
See also How can I make an Image with a transparent Backround in Pygame?

batch crop quad images with diffenrent sizes to a circle

I have lots of images of planets in differing sizes like
They are all positioned exactly in the middle of the square images but with different height.
Now I want to crop them and make the black border transparent. I tried with convert (ImageMagick 6.9.10-23) like this:
for i in planet_*.jpg; do
nr=$(echo ${i/planet_/}|sed s/.jpg//g|xargs)
convert $i -fuzz 1% -transparent black trans/planet_${nr}.png
done
But this leaves some artifacts like:
Is there a command to crop all images in a circle, so the planet is untouched? (It mustn't be imagemagick).
I could also imagine a solution where I would use a larger -fuzz value and then fill all transparent pixels in the inner planet circle with black.
Those are all planets, I want to convert: download zip
Here is one way using Python Opencv from the minEclosingCircle.
Input:
import cv2
import numpy as np
import skimage.exposure
# read image
img = cv2.imread('planet.jpg')
h, w, c = img.shape
# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
# get contour
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# get enclosing circle
center, radius = cv2.minEnclosingCircle(big_contour)
cx = int(round(center[0]))
cy = int(round(center[1]))
rr = int(round(radius))
# draw outline circle over input
circle = img.copy()
cv2.circle(circle, (cx,cy), rr, (0, 0, 255), 1)
# draw white filled circle on black background as mask
mask = np.full((h,w), 0, dtype=np.uint8)
cv2.circle(mask, (cx,cy), rr, 255, -1)
# antialias
blur = cv2.GaussianBlur(mask, (0,0), sigmaX=2, sigmaY=2, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(blur, in_range=(127,255), out_range=(0,255))
# put mask into alpha channel to make outside transparent
imgT = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
imgT[:,:,3] = mask
# crop the image
ulx = int(cx-rr+0.5)
uly = int(cy-rr+0.5)
brx = int(cx+rr+0.5)
bry = int(cy+rr+0.5)
print(ulx,brx,uly,bry)
crop = imgT[uly:bry+1, ulx:brx+1]
# write result to disk
cv2.imwrite("planet_thresh.jpg", thresh)
cv2.imwrite("planet_circle.jpg", circle)
cv2.imwrite("planet_mask.jpg", mask)
cv2.imwrite("planet_transparent.png", imgT)
cv2.imwrite("planet_crop.png", crop)
# display it
cv2.imshow("thresh", thresh)
cv2.imshow("circle", circle)
cv2.imshow("mask", mask)
cv2.waitKey(0)
Threshold image:
Circle on input:
Mask image:
Transparent image:
Cropped transparent image:
packages to install
sudo apt install python3-opencv python3-sklearn python3-skimage

Adjust Image on button

How can I adjust an image to a button in Tkinter?
Actually i have this :
originalImg = Image.open(currentphotofolderPath + file)
img = ImageTk.PhotoImage(originalImg)
Button(photoFrame, image = img, borderwidth=0, height = 200, width = 200)
The problem the image does not adjust to the button with 200x200
I don't want to resize the image with PhotoImage.resize()
The zoom() function should fix your issue:
Return a new PhotoImage with the same image as this widget but zoom it
with X and Y.
Adding the code line below before instantiating the Button() widget should be helpful:
originalImg = Image.open(currentphotofolderPath + file)
originalImg.zoom(200, 200)
img = ImageTk.PhotoImage(originalImg)
Button(photoFrame, image=img, borderwidth=0, height=200, width=200)
you have a couple of choices, the zoom function as posted by Billal, or you create a resize function:
def Resize_Image(image, maxsize):
r1 = image.size[0]/maxsize[0] # width ratio
r2 = image.size[1]/maxsize[1] # height ratio
ratio = max(r1, r2)
newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio))
image = image.resize(newsize, Image.ANTIALIAS)
return image
which will then resize the Image (not the PhotoImage) to the biggest possible size while retaining the aspect ratio (without knowing it beforehand)
note that the resize method should use less memory than the zoom method (if thats an important factor)

Resize an image along rows only or columns only in matlab

I'm writing a function in matlab to zoom or shrink an image using bicubic interpolation. However, my function resizes an image along both rows and columns. What if I want to enlarge the image along the rows only, or along the columns only? This is my code so far
function pic_new = zoom_image(pic, zoom_value)
actualSize = size(pic);
newSize = max(floor(zoom_value.*actualSize(1:2)),1);
newX = ((1:newSize(2))-0.5)./zoom_value+0.5; %# New image pixel X coordinates
newY = ((1:newSize(1))-0.5)./zoom_value+0.5;
oldClass = class(pic); %# Original image type
pic = double(pic); %# Convert image to double precision for interpolation
if numel(actualSize) == 2
pic_new = interp2(pic,newX,newY(:),'cubic');
end
pic_new = cast(pic_new,oldClass);
end
Updated: I was able to resize the image both along rows and columns. However, it doesn't work right
This is the original image: https://imagizer.imageshack.us/v2/895x383q90/r/903/4jM76I.png
This is the image after being enlarge 2.5 along rows and shrunk 1.3 along columns: https://imagizer.imageshack.us/v2/323x465q90/r/673/EHIaoB.png
Why is there such a black box in the result image?
Updated 2: This is how I did: in the command window type
>> img = imread('pic.pgm');
>> newImage = zoom_image(img, 2.5, 1/1.3);
>> imshow(newImage)
Using imresize it can be easily achieved
pic_new = imresize( pic, newSize, 'bicubic' );
Where newSize is the new size of the image (height and width). The new aspect ratio can be arbitrary and does not have to be the same as the aspect ratio of the new image.
For example, shrinking an image by 1/2 along the rows and leaving number of columns unchanged:
[nCol nRows] = size( pic(:,:,1) );
pic_new = imresize( pic, [nCols round( nRows/2 ) ], 'bicubic' );
Try editing your function to have a separate zoom for rows and columns, a la
function pic_new = zoomfunc(pic, zoom_valueX, zoom_valueY)
actualSize = size(pic);
newSize = max(floor([zoom_valueY zoom_valueX].*actualSize(1:2)),1);
newX = ((1:newSize(2))-0.5)./zoom_valueX+0.5; %# New image pixel X coordinates
newY = ((1:newSize(1))-0.5)./zoom_valueY+0.5;
oldClass = class(pic); %# Original image type
pic = double(pic); %# Convert image to double precision for interpolation
if numel(actualSize) == 2
pic_new = interp2(pic,newX,newY(:),'cubic');
end
pic_new = cast(pic_new,oldClass);
end

Extra pixels at top and bottom of label with image in tkinter

In Python 3.2.5 and tkinter, I keep getting 1 or 2 "extra" border-like pixels at the top and bottom of a middle label (of three stacked labels) containing an image (the sides do not have a white border). No matter how I shrink the window or designate no borderwidth, I get a couple of pixels beyond the top and bottom of the label(s).
Here's the code....
root.withdraw()
LoginErrorMsg = tk.Toplevel()
LoginErrorMsg.title('ERROR! Unauthorized Login Attempt')
# pick .gif file
# load the file and covert it to a tkinter image object
imageFile = "ErrorLogin.gif"
image1 = tk.PhotoImage(file=imageFile)
#set size of Error Message Window
width = 348
height = 480
#get size of the whole screen
xmax = LoginErrorMsg.winfo_screenwidth()
ymax = LoginErrorMsg.winfo_screenheight()
#calculate centered window coordinates and position it
x0 = xmax/2 - width/2
y0 = ymax/2 - height/2
LoginErrorMsg.geometry("%dx%d+%d+%d" % (width, height, x0, y0))
# make the root window the size of the image
#root.geometry("%dx%d+%d+%d" % (325, 475, x+100, y+100))
# remove tk icon
LoginErrorMsg.wm_iconbitmap('Ethicsicon.ico')
#if user closes Error Message Window, clean up memory
LoginErrorMsg.protocol("WM_DELETE_WINDOW", handler)
# root has no image argument, so use a label as a panel
panel1 = tk.Label(LoginErrorMsg, text="\nYou have exceeded three (3) login attempts.", background = "white", borderwidth = 0)
#panel1.configure(background = 'white')
panel1.pack(side='top', fill='both', expand='yes')
panel2 = tk.Label(LoginErrorMsg, image=image1, background = "white", borderwidth = 0)
panel2.pack(side='top', fill='both', expand='yes')
panel3 = tk.Label(LoginErrorMsg, text="", borderwidth = 0)
panel3.pack(side='top', fill= 'both', expand='yes')
# save the panel's image from 'garbage collection'
panel1.image = image1
# put a button on the image panel
button2 = tk.Button(panel3, text = ' OK ', command = LeaveSystemCallback)
button2.pack(side='top')
LoginErrorMsg.update()
Try changing the highlightthickness attribute of each label to zero.

Resources