How to Crop an Image with Pygame

How can I crop an image with Pygame?

cropped = pygame.Surface((80, 80))
cropped.blit(buttonStates, (0, 0), (30, 30, 80, 80))

The blit method on a surface 'pastes' another surface on to it. The first argument to blit is the source surface. The second is the location to paste to (in this case, the top left corner). The third (optional) argument is the area of the source image to paste from -- in this case an 80x80 square 30px from the top and 30px from the left.

How to Crop a Rotated Image In Pygame

Is this the effect that you want to achieve? You have to calculate the new crop position after each rotation, because the size of the rotated image changes all the time. In the upper left corner you see how the parent image/surface changes.

import sys
import pygame as pg

def main():
pg.init()
screen = pg.display.set_mode((320, 240))
clock = pg.time.Clock()
done = False
bg_color = pg.Color(50, 80, 120)
# Create the original image that we use for the rotation
# to avoid visual degeneration.
orig_img = pg.Surface((100, 100))
orig_img.fill((20, 50, 130))
pg.draw.circle(orig_img, pg.Color('springgreen2'), (30, 30), 30)
pg.draw.circle(orig_img, pg.Color('lightgoldenrod3'), (60, 60), 30)
pg.draw.rect(orig_img, pg.Color('black'), (48, 0, 4, 100))
pg.draw.rect(orig_img, pg.Color('black'), (0, 48, 100, 4))
rect = orig_img.get_rect(topleft=(30, 30))

angle = 0

while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True

angle += 1
# Rotate the image.
img = pg.transform.rotate(orig_img, angle)
rect = img.get_rect(center=rect.center)
# Calculate new crop position. Half of the new
# width & height minus half of the original dimensions.
x, y = rect.width/2 - 50, rect.height/2 - 50
sub = img.subsurface((x, y, 100, 50))

screen.fill(bg_color)

screen.blit(img, rect)
screen.blit(sub, (150, 150))

pg.display.flip()
clock.tick(30)

if __name__ == "__main__":
main()
pg.quit()
sys.exit()

Image Cropping using Python

I took a quick look and fixed a few other problems along the way. Essentially my changes do this:

  • Draw the bounding box on a temporary image, set its alpha transparency, and then blit this over top of the main image.
  • Avoid extraneous drawing cycles (when the mouse isn't moving, no sense in drawing the same image again).
  • Ensure that width and height are always positive. If the rect is drawn by dragging the mouse left or up, your code would end up with a negative width and/or height, raising an exception when trying to write the final image.

Here is a screenshot of running the fixed code:

Sample Image

I split the code into two parts to avoid the scrollbars:

import pygame, sys
from PIL import Image
pygame.init()

def displayImage(screen, px, topleft, prior):
# ensure that the rect always has positive width, height
x, y = topleft
width = pygame.mouse.get_pos()[0] - topleft[0]
height = pygame.mouse.get_pos()[1] - topleft[1]
if width < 0:
x += width
width = abs(width)
if height < 0:
y += height
height = abs(height)

# eliminate redundant drawing cycles (when mouse isn't moving)
current = x, y, width, height
if not (width and height):
return current
if current == prior:
return current

# draw transparent box and blit it onto canvas
screen.blit(px, px.get_rect())
im = pygame.Surface((width, height))
im.fill((128, 128, 128))
pygame.draw.rect(im, (32, 32, 32), im.get_rect(), 1)
im.set_alpha(128)
screen.blit(im, (x, y))
pygame.display.flip()

# return current box extents
return (x, y, width, height)

And part 2 (concatenate to the above):

def setup(path):
px = pygame.image.load(path)
screen = pygame.display.set_mode( px.get_rect()[2:] )
screen.blit(px, px.get_rect())
pygame.display.flip()
return screen, px

def mainLoop(screen, px):
topleft = bottomright = prior = None
n=0
while n!=1:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONUP:
if not topleft:
topleft = event.pos
else:
bottomright = event.pos
n=1
if topleft:
prior = displayImage(screen, px, topleft, prior)
return ( topleft + bottomright )

if __name__ == "__main__":
input_loc = 'stack.png'
output_loc = 'out.png'
screen, px = setup(input_loc)
left, upper, right, lower = mainLoop(screen, px)

# ensure output rect always has positive width, height
if right < left:
left, right = right, left
if lower < upper:
lower, upper = upper, lower
im = Image.open(input_loc)
im = im.crop(( left, upper, right, lower))
pygame.display.quit()
im.save(output_loc)

Any way to deal with cropping sprite images - Python/Pygame

I recently created a chess program in which I used a spritesheet including all chess pieces. First I imported the spritesheet and transformed it to the correct size I needed (SQW is the with of each square on the chess board). Then I cropped it in a nested for loop to obtain a list of piece images. I needed a nested for loop since the spritesheet contained 2 rows with 6 piece images, black and white pieces on each row. Code looks somthing like this:

SPRITE = pygame.transform.smoothscale(pygame.image.load('imgs/pieces.png'), (int(SQW*6), int(SQW*2)))
PIECES = [None] * 12
for i in range(2):
for j in range(6):
PIECES[j + i*6] = pygame.Surface.subsurface(SPRITE, (j*SQW, i*SQW, SQW, SQW))

Is there a function in Pygame to save a portion of the screen as an image?

screen is a Surface object which has method subsurface(Rect) to get part of surface - and this part will be also a Surface object.

And there is function pygame.image.save(surface, filename)


Other method: at start create smaller Surface and draw on this surface (instead of drawing directly on screen) and blit() this surface on screen to display it and save() this surface to file.

You can even use many surfaces and keep them on list to create function Undo. When you use new tool then you duplicate surface and put it on list. When you use Undo then you get last surface from list.

Temporary save Image in pygame

The following function converts a OpenCV (cv2) image respectively a numpy.array (that's the same) to a pygame.Surface:

import numpy as np
def cv2ImageToSurface(cv2Image):
if cv2Image.dtype.name == 'uint16':
cv2Image = (cv2Image / 256).astype('uint8')
size = cv2Image.shape[1::-1]
if len(cv2Image.shape) == 2:
cv2Image = np.repeat(cv2Image.reshape(size[1], size[0], 1), 3, axis = 2)
format = 'RGB'
else:
format = 'RGBA' if cv2Image.shape[2] == 4 else 'RGB'
cv2Image[:, :, [0, 2]] = cv2Image[:, :, [2, 0]]
surface = pygame.image.frombuffer(cv2Image.flatten(), size, format)
return surface.convert_alpha() if format == 'RGBA' else surface.convert()

See How do I convert an OpenCV (cv2) image (BGR and BGRA) to a pygame.Surface object for a detailed explanation of the function.

how to make circular surface in pygame

The background is a square image. It should be cropped into the shape of a circle and rendered on screen

You can achieve this by using the blend mode BLEND_RGBA_MIN (see pygame.Surface.blit).

Create a transparent pygame.Surface with the same size as self.background. Draw a whit circle in the middle of the Surface and blend the background on this Surface using the blend mode BLEND_RGBA_MIN. Finally you can blit it on the screen:

size = self.background.get_size()
self.cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(self.cropped_background, (255, 255, 255, 255), (0, 0, *size))
self.cropped_background.blit(self.background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)
self.ss.fill(BLACK)
self.ss.blit(self.cropped_background, ORIGIN)

Minimal example: Sample Image repl.it/@Rabbid76/PyGame-ClipCircularRegion-1

Sample Image

import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))

background = pygame.Surface(window.get_size())
for x in range(5):
for y in range(5):
color = (255, 255, 255) if (x+y) % 2 == 0 else (255, 0, 0)
pygame.draw.rect(background, color, (x*50, y*50, 50, 50))

size = background.get_size()
cropped_background = pygame.Surface(size, pygame.SRCALPHA)
pygame.draw.ellipse(cropped_background, (255, 255, 255, 255), (0, 0, *size))
cropped_background.blit(background, (0, 0), special_flags=pygame.BLEND_RGBA_MIN)

run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False

window.fill(0)
window.blit(cropped_background, (0, 0))
pygame.display.flip()

See Also How to fill only certain circular parts of the window in pygame?



Related Topics



Leave a reply



Submit