Remove border around image in Pygame - python

I have got some surfaces in Pygame with a transparent background. They're all the same size. But there's a different sized circle drawn on each of them, so the circle doesn't exactly fit the image.
Here are some example images (I took a screenshot in Photoshop so you can clearly see the transparency and the size of the images):
Now I want to remove the transparent border around the image so the circle exactly fits into the image. I don't want the surface to be circle shaped, I don't think that's possible, but I want that the surface doesn't have blank columns on the left and right and that it doesn't have any blank rows on the top and the bottom. The wanted results:
The circle on the surfaces changes size every frame so I have to recalculate the new surfaces every frame.
I already Googled it, but I haven't found anything for Pygame surfaces yet. I also tried making my own function but it looks ugly and much worse: the framerate drops from 50 (if I don't call the function) to 30 fps (if I do call the function). I tested it a little bit and I found out that smaller circles take longer to process than bigger circles. How can I do this, but faster. If you want I can show the function I made.

The surface object has a method called get_bounding_rect which is where we will start. The function returns the smallest rect possible which contains all of the non-transparent pixels on the surface.
pixel_rect = image.get_bounding_rect()
With the size of this rect, we can create a new surface:
trimmed_surface = pygame.Surface(pixel_rect.size)
Now blit the portion of image contained within pixel_rect onto trimmed_surface:
trimmed_surface.blit(image, (0,0), pixel_rect)
At this point, trimmed_surface should be a surface the same size as pixel_rect, with the unwanted transparent rows and columns "trimmed" off of the original surface.
Documentation for Surface.get_bounding_rect: http://www.pygame.org/docs/ref/surface.html#Surface.get_bounding_rect

Related

Resize Image Content but Keep Image Dimensions

I am trying to make a basic Pokemon replica in Python using PyGame. I have followed a few tutorials concerning this library in the past. So, for this project, I will have a basic tile structure, either grass or water. The player is not allowed to move onto a water tile but can move freely on the grass tile. The sprite I obtained via the tutorial is of dimensions 64 x 64 and it occupies the entire space of the tile, so there are no transparent pixels around the boundaries.
However, for my purposes, I need to use my own sprites (64 x 64) which I got from a sprite sheet. However, the sprites, in this case, have the top half of the image as just transparent, unused pixels. The same applies to the quarters on each side of the sprite. So, I need to batch resize all those sprites by multiplying the visible pixel size by 2 along both axes. IE, just increase the sprite size by scale factor 2. I could easily do that, but it would still be ineffective since the new sprite size would be 128 x 128 and the ratio of unused pixels stays the same.
My question:
How can I resize the sprites by scale factor 2 and yet retain the original dimensions of 64 x 64 by just disposing of the transparent pixels which would be outside of the 64 x 64 frame?
I need a method which I could use for all 9600 sprites I have, preferably in Python, even though I wouldn't mind a solution with some other software.
I tried looking for solutions on the web but I don't know how to properly phrase this question.
Create an empty pygame.Surface object with transparent alpha and a size of 32x32:
new_image = pygame.Surface((32, 32), pygame.SRCALPHA)
blit the desired region (32, 16, 32, 32) form the original 64x64 Sprite (original_image) on to the new surface:
new_image.blit(original_image, (0, 0), (32, 16, 32, 32))
Scale the new Surface object by pygame.transform.smoothscale
new_image = pygame.transform.smoothscale(new_image, (64, 64))
If these are images you can easily do this using PIL.
from PIL import Image
Ignore this bit which just makes a 64x64 test image
from scipy import misc
img = misc.face()
img=Image.fromarray(np.uint8(img))
img=img.resize((64,64))
This should show a cute little raccoon
Next we can resize by a scale factor of two as you describe (NOTE: I'm not taking advantage of the fact that this is a square image just so this is more generalizable. You could simplify this a bit with imsz=img.size[0] )
imsz=img.size
# resize by factor of 2
img=img.resize((imsz[0]*2,imsz[1]*2))
Which should give a 2x scaled image
Now we just crop it
# box – The crop rectangle, as a (left, upper, right, lower)-tuple
crpimg=img.crop(box=(int(0.5*imsz[0]),int(0.5*imsz[1]),int(1.5*imsz[0]),int(1.5*imsz[1])))
Which gives us a 64x64 image:
#Rabbid76's answer of copying/blit'ing a section of the original and then enlarging/scaling it is a good option and probably the way I would go.
However, if you have already resized them to the larger size and just want a cropped down version of an existing image/surface, you can use subsurface(). That will give you a surface that is really like a virtual surface that provides a window into a subsection of the original surface. It references the same pixels as the original surface, so if one is changed it is visible in the other as well. That does not seem an issue for what you are doing though.

Pygame Surface Positioning

As I understood pygame drawing method, the 2nd argument of the blit function (screen.blit(surface, (0,0))) tells pygame where to draw the given surface (like an offset to start drawing from, or rather a placement of the surface on the screen). Although, from recent experimenting, it seems that pygame surfaces placements are fixed, and that (0,0) is used to crop the surface before pygame blit it to the screen, for efficiency purposes.
Are surfaces placement really fixed (is my latter observation correct)? and if so, is there another way to conveniently move an already drawn surface to another position on the screen? Or should i implement my own way of moving complex "drawable" objects?
Thanks!
A pygame.Surface object has no position, it has a size only. Note, the location of the Rect object which is returned by pygame.Surface.get_rect() is always (0, 0).
When you blit a Surface on another Surface, then each pixel is copied and placed at the corresponding position of the destination Surface. Thus always a position has to be specified, when a Surface is blit on a destination Surface.
See also Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
[...] if so, is there another way to conveniently move an already drawn surface to another position [...]
You have a basic misunderstanding. A Surface cannot be "moved". A Surface is copied on the Surface object which is associated to the game window.
A Surface appears to be moving, because the entire scene is drawn in every frame. First the background is drawn, then the objects (Sprites, Surfaces) are drawn on top of the background and finally the display is updated (in every frame). If an object is placed at a slightly different position in every frame, then the object appears to be moving smoothly.

python imshow pixel size varies within plot

Dear stackoverflow community!
I need to plot a 2D-map in python using imshow. The command used is
plt.imshow(ux_map, interpolation='none', origin='lower', extent=[lonhg_all.min(), lonhg_all.max(), lathg_all.min(), lathg_all.max()])
The image is then saved as follows
plt.savefig('rdv_cr%s_cmlon%s_ux.png' % (2097, cmlon_ref))
and looks like
The problem is that when zooming into the plot one can notice that the pixels have different shapes (e.g. different width). This is illustrated in the zoomed part below (taken from the top region of the the first image):
Is there any reason for this behaviour? I input a rectangular grid for my data, but the problem does not have to do with the data itself, I suppose. Instead it is probably something related to rendering. I'd expect all pixels to be of equal shape, but as could be seen they have both different widths as well as heights. By the way, this also occurs in the interactive plot of matplotlib. However, when zooming in there, they become equally shaped all of a sudden.
I'm not sure as to whether
https://github.com/matplotlib/matplotlib/issues/3057/ and the link therein might be related, but I can try playing around with dpi values. In any case, if anybody knows why this happens, could that person provide some background on why the computer cannot display the plot as intended using the commands from above?
Thanks for your responses!
This is related to the way the image is mapped to the screen. To determine the color of a pixel in the screen, the corresponding color is sampled from the image. If the screen area and the image size do not match, either upsampling (image too small) or downsampling (image too large) occurs.
You observed a case of upsampling. For example, consider drawing a 4x4 image on a region of 6x6 pixels on the screen. Sometimes two screen pixels fall into an image pixel, and sometimes only one. Here, we observe an extreme case of differently sized pixels.
When you zoom in in the interactive view, this effect seems to disapear. That is because suddenly you map the image to a large number of pixels. If one image pixel is enlarged to, say, 10 screen pixels and another to 11, you hardly notice the difference. The effect is most apparent when the image nearly matches the screen resolution.
A solution to work around this effect is to use interpolation, which may lead to an undesirable blurred look. To reduce the blur you can...
play with different interpolation functions. Try for example 'kaiser'
or up-scale the image by a constant factor using nearest neighbor interpolation (e.g. replace each pixel in the image by a block of pixels with the same color). Then any blurring will only affect the edges of the block.

Pygame Large Surfaces

I'm drawing a map of a real world floor with dimensions roughly 100,000mm x 200,000mm.
My initial code contained a function that converted any millimeter based position to screen positioning using the window size of my pygame map, but after digging through some of the pygame functions, I realized that the pygame transformation functions are quite powerful.
Instead, I'd like to create a surface that is 1:1 scale of real world and then scale it right before i blit it to the screen.
Is this the right way to be doing this? I get an error that says Width or Height too large. Is this a limit of pygame?
I dont fully understand your question, but to attempt to answer it here is the following.
No you should not fully draw to the screen then scale it. This is the wrong approach. You should tile very large surfaces and only draw the relevant tiles. If you need a very large view, you should use a scaled down image (pre-scaled). Probably because the amount of memory required to draw an extremely large surface is prohibitive, and scaling it will be slow.
Convert the coordinates to the tiled version using some sort of global matrix that scales everything to the size you expect. So you should also filter out sprites that are not visible by testing their inclusion inside the bounding box of your view port. Keep track of your view port position. You will be able to calculate where in the view port each sprite should be located based on its "world" coordinates.
If your map is not dynamic, I would suggest draw a map outside the game and load it in game.
If you plan on converting the game environment into a map, It might be difficult for a large environment. 100,000mm x 200,000mm is a very large area when converting into a pixels. I would suggest you to scale it down before loading.
As for scaling in-game, you can use pygame.transform.rotozoom or pygame.transform.smoothscale.
Also like the first answer mentions, scaling can take significant memory and time for very large images. Scaling a very large image to a very small image can make the image incomprehensible.

How to avoid copying the level Surface every frame in worms-like game?

I am working on a game that has destructible terrain (like in the game Worms, or Scorched Earth) and uses pixel perfect collision detection via masks.
The level is a single surface and how it works now is that I create a copy every frame, draw all sprites that need drawing on it, then blit the visible area to the display surface.
Is there any way to avoid copying the whole level surface every frame and still be able to use the pixel perfect collision tools found in pygame?
I tried blitting the level surface first, then blitting every sprite on the screen (with their blit coordinates adjusted by the camera, except for the player character whose coordinates are static), but in that case the collision detection system falls apart and I can't seem to be able to fix it.
UPDATE
I have managed to make it work the following way:
When drawing the sprites, I convert their game world coordinates (which are basically coordinates relative to the origin of the level bitmap) to screen coordinates (coordinates relative to the camera, which is the currently visible area of the level).
During the collision detection phase I use the coordinates and bounding boxes that are positioned relative to the level surface; so just like above. The thing is that the camera's position is bound to the player's position which is not and should not have been a static value (I am really not sure how I managed to not realize that for so long).
While this fixes my problem, the answer below is a much more comprehensive look on how to improve performance in a situation like this.
I am also open to suggestions to use other libraries that would make the ordeal easier, or faster. I have thought about pyglet and rabbyt, but it looks like the same problem exists there.
This is an issue that used to come up a lot in the days before graphics accelerators, when computers were slow. You basically want to minimize the work required to refresh the screen. You are on the right track, but I recommend the following:
Keep a copy of the background available offscreen, as you are doing
now.
Allocate a working bitmap that is the same size as the screen.
For each sprite, compute the bounding rectangle (bounding box) for
its new and old positions.
If the new and old bounding boxes overlap, combine them into one
larger box. If they do not overlap, treat them separately.
Group all the bounding boxes into sets that overlap. They might all
end up in one set (when the sprites are close to each other), or
each bounding box might be in a set by itself (when the sprites are
far apart).
Copy the background to regions of the working bitmap corresponding
to each bounding box set.
Copy the sprites for each set to the working bitmap in their new
positions (in the correct z-order, of course!).
Finally, copy the finished offscreen bitmap to the display surface,
set bounding box by set bounding box.
This approach minimizes the amount of copying that you have to do, both of background and sprite. If the sprites are small relative to the display area, the savings should be significant. The worst case is where the sprites are all arranged on a diagonal line, just barely overlapping each other. In this case, you might want to switch to a more generalized bounding shape than a box. Take a look at QuickDraw Regions for an example: Wikipedia Discussion Patent Source.
Now, you may be thinking that the work to group the bounding boxes into sets is a O(n^2) operation, and you would be right. But it grows only with the square of the number of sprites. 16 sprites implies 256 comparisons. That's probably less work than a single sprite blit.
I focused on minimizing the pixel copying work. I must admin I am not familiar with the particulars of your collision detection library, but I get the idea. Hopefully that is compatible with the algorithm I have proposed.
Good luck. If you finish the game and post it online, put a link to it in your question or a comment.

Categories

Resources