How to embed skia python surface inside pysdl2 - python

I am trying to embed skia-python's surface inside a window rather than output to a image file. I am using pysdl2 to create the window using the following code from the documentation:
import sys
import sdl2.ext
RESOURCES = sdl2.ext.Resources(__file__, "resources")
sdl2.ext.init()
window = sdl2.ext.Window("Hello World!", size=(640, 480))
window.show()
factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE)
sprite = factory.from_image(RESOURCES.get_path("hello.bmp"))
spriterenderer = factory.create_sprite_render_system(window)
spriterenderer.render(sprite)
processor = sdl2.ext.TestEventProcessor()
processor.run(window)
sdl2.ext.quit()
And this code to create the surface from skia's documentation:
import skia
surface = skia.Surface(128, 128)
with surface as canvas:
rect = skia.Rect(32, 32, 96, 96)
paint = skia.Paint(
Color=skia.ColorBLUE,
Style=skia.Paint.kFill_Style)
canvas.drawRect(rect, paint)
image = surface.makeImageSnapshot()
image.save('output.png', skia.kPNG)
Now what I want to achieve is to take the image (or surface whichever applicable) object from the skia portion and plug it into pysdl2 so that I can draw with skia but handle window's event loop with pysdl2 and I'd like to avoid ctypes right now because I am not so familiar with it.

I gave up on creating it without ctypes as all we need from it is ctypes.byref and I am now importing sdl2 instead of sdl2.ext which was more pythonic, but also restricted a bit of functionality that is required here.
Now to answer the question, I followed this guide here (if you are not building a browser it might go a little off topic for you)
So I have also implemented a general enough version from the above guide, you can draw to Window.skia_surface and then call Window.update to copy skia surface to the window screen:
import skia
import sdl2 as sdl
from ctypes import byref
def sdl_event_loop():
event = sdl.SDL_Event()
running = True
while running:
pending_events = sdl.SDL_PollEvent(byref(event))
while pending_events != 0:
# QUIT HANDLER
if event.type == sdl.SDL_QUIT:
running = False
sdl.SDL_Quit()
break
# UPDATE PENDING EVENTS
pending_events = sdl.SDL_PollEvent(byref(event))
class Window:
DEFAULT_FLAGS = sdl.SDL_WINDOW_SHOWN
BYTE_ORDER = {
# ---------- -> RED GREEN BLUE ALPHA
"BIG_ENDIAN": (0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff),
"LIL_ENDIAN": (0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)
}
PIXEL_DEPTH = 32 # BITS PER PIXEL
PIXEL_PITCH_FACTOR = 4 # Multiplied by Width to get BYTES PER ROW
def __init__(self, title, width, height, x=None, y=None, flags=None):
self.title = bytes(title, "utf8")
self.width = width
self.height = height
# Center Window By default
self.x, self.y = x, y
if x is None:
self.x = sdl.SDL_WINDOWPOS_CENTERED
if y is None:
self.y = sdl.SDL_WINDOWPOS_CENTERED
# Override flags
self.flags = flags
if flags is None:
self.flags = self.DEFAULT_FLAGS
# SET RGBA MASKS BASED ON BYTE_ORDER
is_big_endian = sdl.SDL_BYTEORDER == sdl.SDL_BIG_ENDIAN
if is_big_endian:
self.RGBA_MASKS = self.BYTE_ORDER["BIG_ENDIAN"]
else:
self.RGBA_MASKS = self.BYTE_ORDER["LIL_ENDIAN"]
# CALCULATE PIXEL PITCH
self.PIXEL_PITCH = self.PIXEL_PITCH_FACTOR * self.width
# SKIA INIT
self.skia_surface = self.__create_skia_surface()
# SDL INIT
sdl.SDL_Init(sdl.SDL_INIT_EVENTS) # INITIALIZE SDL EVENTS
self.sdl_window = self.__create_SDL_Window()
sdl_event_loop()
def __create_SDL_Window(self):
window = sdl.SDL_CreateWindow(
self.title,
self.x, self.y,
self.width, self.height,
self.flags
)
return window
def __create_skia_surface(self):
surface_blueprint = skia.ImageInfo.Make(
self.width, self.height,
ct=skia.kRGBA_8888_ColorType,
at=skia.kUnpremul_AlphaType
)
surface = skia.Surface.MakeRaster(surface_blueprint)
return surface
def __pixels_from_skia_surface(self):
image = self.skia_surface.makeImageSnapshot()
pixels = image.tobytes()
return pixels
def __transform_skia_surface_to_SDL_surface(self):
pixels = self.__pixels_from_skia_surface()
sdl_surface = sdl.SDL_CreateRGBSurfaceFrom(
pixels,
self.width, self.height,
self.PIXEL_DEPTH, self.PIXEL_PITCH,
*self.RGBA_MASKS
)
return sdl_surface
def update(self):
rect = sdl.SDL_Rect(0, 0, self.width, self.height)
window_surface = sdl.SDL_GetWindowSurface(self.sdl_window) # the SDL surface associated with the window
transformed_skia_surface = self.__transform_skia_surface_to_SDL_surface()
# Transfer skia surface to SDL window's surface
sdl.SDL_BlitSurface(
transformed_skia_surface, rect,
window_surface, rect
)
# Update window with new copied data
sdl.SDL_UpdateWindowSurface(self.sdl_window)
if __name__ == "__main__":
window = Window("Browser Test", 500, 500, flags=sdl.SDL_WINDOW_SHOWN | sdl.SDL_WINDOW_RESIZABLE)
Explanation: Primarily the above code does four things create a skia surface that you will draw on, create an SDL window, convert the skia surface to a SDL surface and at last copy the data in the newly created surface to the SDL surface associated with the window and update it. For a bit more explanation I recommend you look into the above guide and also check out skia-python docs and SDL2's API Reference.

Related

PyGame colliders don't scale with window

I should point out that I'm a beginner with PyGame. I have made a program that displays some simple graphics on the screen using PyGame. It blits every graphic on a dummy surface and the dummy surface gets scaled and blit to a 'real' surface that gets displayed on the screen in the end. This allows the program to have a resizable window without messing the graphics and UI.
I have also made my own 'Button' class that allows me to draw clickable buttons on the screen. Here it is:
import pygame
pygame.font.init()
dfont = pygame.font.Font('font/mfdfont.ttf', 64)
#button class button(x, y, image, scale, rot, text_in, color, xoff, yoff)
class Button():
def __init__(self, x, y, image, scale = 1, rot = 0, text_in = '', color = 'WHITE', xoff = 0, yoff = 0):
self.xoff = xoff
self.yof = yoff
self.x = x
self.y = y
self.scale = scale
width = image.get_width()
height = image.get_height()
self.image = pygame.transform.rotozoom(image, rot, scale)
self.text_in = text_in
self.text = dfont.render(self.text_in, True, color)
self.text_rect = self.text.get_rect(center=(self.x +width/(2/scale) + xoff, self.y + height/(2/scale) + yoff))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.clicked = False
def draw(self, surface):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check mouseover and clicked conditions
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
action = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button on screen
surface.blit(self.image, (self.rect.x, self.rect.y))
surface.blit(self.text, self.text_rect)
return action
When I need to draw one of these buttons on the screen I firstly define it like this:
uparrow = button.Button(128, 1128, arrow_img, 0.5, 0, "SLEW", WHITE, 0, 128)
Then I call it's draw function like this:
if uparrow.draw(screen):
print('UP')
It works reasonably well when drawing it to a surface that doesn't get scaled. This is the problem. When I scale the dummy surface that it gets drawn to, the button's image and text scale just fine but it's collider does not. So when I click on it nothing happens, but if I click on the location of the screen the button would have been on the unscaled dummy surface it works.
Just for context, the dummy surface is 2048x1024 and the 'real' surface is much smaller, starting at 1024x512 and going up and down however the user resizes the window. The game maintains a 2:1 aspect ratio though, so any excess pixels in the game window are black. You can see this in the screenshot below:
Above is a screenshot of the game window. You can see the 'NORM' button at the top of the game screen, and the red box that roughly represents the same 'NORM' button's actual collider. It's basically where it would be on the dummy surface.
(I have previously posted a question on somewhat the same problem as this one, but at that time I didn't know the colliders actually worked and I thought my clicks just didn't register on the buttons, which is not the case).
I'd like to know what part of my button class causes this and how it should be refactored to fix this issue. Alternatively, if you think it's caused by my double surface rendering technique or anything else really, please do point me in the right direction.
In your setup you draw the buttons on an surface, scale the surface and blit that surface on the display. So you do something like the following:
dummy_surface = pygame.Surface((dummy_width, dummy_height)
while True:
# [...]
scaled_surface = pygame.transform.scale(dummy_surface, (scaled_width, scaled_height))
screen.blit(scaled_surface, (offset_x, offset_y))
For click detection to work on the original buttons, you must scale the position of the mouse pointer by the reciprocal scale and shift the mouse position by the inverse offset:
def draw(self, surface):
action = False
# get mouse position
pos = pygame.mouse.get_pos()
scale_x = scaled_width / dummy_surface.get_width()
scale_y = scaled_height / dummy_surface.get_height()
mx = int((pos[0] - offset_x) / scale_x)
my = int((pos[1] - offset_y) / scale_y)
pos = (mx, my)
# [...]

Python + Pyglet: Efficiently updating the image used by a sprite

As the title says. Using batch drawing I get really good performance, even with 4096 sprites. However, since my sprites need to change their underlying image I run into issues with performance. I'm pretty sure I'm doing something silly here, since I specifically created a grid/sprite sheet to handle this effectively. But, of course, I never really use it in any effective manner. I might as well have had 5 different images.
What I really want is to keep the underlying sprite image constant, but shift the visible part based on the "food" metric. Here's the code:
import sys, pyglet, random, time
# Constants.
WIDTH = 1280
HEIGHT = 960
TARGET_FPS = 60
GROWTH_CHANCE = 0.1
fps = 0
screen = pyglet.window.Window(WIDTH, HEIGHT)
random.seed(time.time())
# Here we load universal assets, images, sounds, etc.
grass_tiles_img = pyglet.image.load('grass_tiles.png')
grass_tiles_grid = pyglet.image.ImageGrid(grass_tiles_img, 1, 5)
# Sprite batches.
grass_batch = pyglet.graphics.Batch()
class GrassTile:
'''Define a grass tile which cows can graze on.'''
def __init__(self, x, y, food):
self.food = food
self.sprite = pyglet.sprite.Sprite(grass_tiles_grid[0], x, y,
batch=grass_batch)
def draw(self):
grid_index = (self.food // 20)
self.sprite.image = grass_tiles_grid[grid_index]
return self.sprite
def grow(self):
if random.random() < GROWTH_CHANCE:
self.food = min(self.food + 1, 99)
#screen.event
def on_close():
sys.exit()
#screen.event
def on_draw():
# Clear the screen.
screen.clear()
# Draw grass.
grass_sprites = []
for grass in grass_tiles:
grass_sprites.append(grass.draw())
grass_batch.draw()
# Draw FPS counter.
label = pyglet.text.Label('FPS: ' + str(fps), 'Times New Roman', 12, 10, 10)
label.draw()
def grow_grass(dt):
for grass in grass_tiles:
grass.grow()
def calculate_fps(dt):
global fps
fps = round(min(pyglet.clock.get_fps(), TARGET_FPS))
grass_tiles = [GrassTile(20 * i, 15 * j, 0) for j in range(64) for i in range(64)]
pyglet.clock.schedule_interval(grow_grass, 1 / TARGET_FPS)
pyglet.clock.schedule_interval(calculate_fps, 1 / TARGET_FPS)
pyglet.app.run()
And here's the image so you can run the code:
https://i.imgur.com/kFe91aA.png
Why not just have the image change during grass.grow()?
You don't need to do anything to the grass in the draw phase except draw the batch. Setting the image of a sprite isn't a draw operation, it just changes texture coordinates.
def grow(self):
if random.random() < GROWTH_CHANCE:
self.food = min(self.food + 1, 99)
grid_index = (self.food // 20)
self.sprite.image = grass_tiles_grid[grid_index]
You also shouldn't be recreating the label every draw frame. Create the label beforehand and just update the text. label.text = f'FPS: {fps}'

Surface display able to properly represent opacity, but any other surface cannot

I am trying to make a tic-tac-toe game with pygame. An important thing I want is being able to make my images (eg. X and O) slightly translucent for when my user is only hovering over a grid tile. I also use opacity to visually show whose turn it is.
This is what I have tried:
x_tile = pygame.image.load('x_tile').convert()
x_tile.set_alpha(100)
This works fine when I'm blitting x_tile directly onto the display like this:
# This is for simplicity's sake. The actual blit process is all being done in an infinite loop
screen = pygame.display.set_mode((300, 300))
screen.blit(x_file, x_file.get_rect())
But my game is using another image that represents the grid, and that is what I'm blitting onto. So I'm blitting this board onto the display, then blitting the actual X and O tiles on the board.
screen = pygame.display.set_mode((300, 300))
screen.blit(board, board_rect)
board.blit(x_tile, x_tile.get_rect(center=grid[0].center)) # I have a list of Rects that make a grid on the board image. grid[0] is the top left
When I do it that way, x_tile.set_alpha(100) seems to have no effect and I don't know what to do.
Edit: I am using pygame 2.0.1. I'm on Windows 10.
Here is the entire code
import os
import pygame
from pygame.locals import *
# Game constants
WIN_SIZE = WIN_WIDTH, WIN_HEIGHT = 800, 600
BLACK = 0, 0, 0
WHITE = 255, 255, 255
RED = 255, 0, 0
BLUE = 0, 0, 255
# Game functions
class NoneSound:
"""dummy class for when pygame.mixer did not init
and there is no sound available"""
def play(self): pass
def load_sound(file):
"""loads a sound file, prepares it for play"""
if not pygame.mixer:
return NoneSound()
music_to_load = os.path.join('sounds', file)
try:
sound = pygame.mixer.Sound(music_to_load)
except pygame.error as message:
print('Cannot load following sound:', music_to_load)
raise SystemExit(message)
return sound
def load_image(file, colorkey=None, size=None):
"""loads image into game"""
image_to_load = os.path.join('images', file)
try:
image = pygame.image.load(image_to_load).convert()
except pygame.error as message:
print('Cannot load following image:', image_to_load)
raise SystemExit(message)
if colorkey is not None:
if colorkey == -1:
colorkey = image.get_at((0, 0))
image.set_colorkey(colorkey, RLEACCEL)
if size is not None:
image = pygame.transform.scale(image, size)
return image
# Game class
class TTTVisual:
"""Controls game visuals"""
def __init__(self, win: pygame.Surface):
self.win = win
# Load in game images
self.board = load_image('board.png', size=(600, 450), colorkey=WHITE)
self.x_tile = load_image('X_tile.png', size=(100, 100), colorkey=BLACK)
self.o_tile = load_image('O_tile.png', size=(100, 100), colorkey=BLACK)
# Translucent for disabled looking tile
self.x_tile_trans = self.x_tile.copy()
self.o_tile_trans = self.o_tile.copy()
self.x_tile_trans.set_alpha(100)
self.o_tile_trans.set_alpha(100)
# Used to let user know whose turn it is
self.x_turn = pygame.transform.scale(self.x_tile, (50, 50))
self.o_turn = pygame.transform.scale(self.o_tile, (50, 50))
self.x_turn_trans = pygame.transform.scale(self.x_tile_trans, (50, 50))
self.o_turn_trans = pygame.transform.scale(self.o_tile_trans, (50, 50))
self.get_rects()
self.grid = self.setup_grid()
def get_rects(self):
"""Creates coords for some visual game assets"""
self.board_rect = self.board.get_rect(
center=self.win.get_rect().center)
self.x_turn_rect = self.x_turn.get_rect(top=10, left=10)
self.o_turn_rect = self.o_turn.get_rect(top=10, left=WIN_WIDTH-60)
def setup_grid(self):
grid = []
left = 0
top = 150
row = 0
for i in range(9):
if (i != 0) and (i % 3 == 0):
row += 1
left = 0
grid.append(pygame.Rect(left, row*top, 200, 150))
left += 200
return grid
def update_turn_status(self):
"""Updates the X and O tiles on the top left and right to
let user know whose turn it is"""
self.win.blits((
(self.x_turn_trans, self.x_turn_rect),
(self.o_turn, self.o_turn_rect)
))
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
def update(self):
self.win.fill(WHITE)
self.update_turn_status()
self.update_grid()
pygame.display.flip()
def main():
pygame.init()
win = pygame.display.set_mode(WIN_SIZE)
tttvisual = TTTVisual(win)
tttfunc = TTTFunc(tttvisual)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
running = False
tttvisual.update()
pygame.quit()
if __name__ == "__main__":
main()
The issue is caused by the line:
self.board.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=self.grid[0].center))
You don't blit the image on the display Surface, but on the self.board Surface. When a Surface is blit, it is blended with the target. When you draw on a Surface, it changes permanently. Since you do that over and over again, in every frame, the source Surface appears to by opaque. When you decrease the alpha value (e.g. self.x_tile_trans.set_alpha(5)), a fade in effect will appear.
Never draw on an image Surface. Always draw on the display Surface. Cleat the display at begin of a frame. Draw the entire scene in each frame and update the display once at the end of the frame.
class TTTVisual:
# [...]
def update_grid(self):
"""Updates board"""
self.win.blit(self.board, self.board_rect)
# Here is where you could change board to win and see that the tile changes in opacity
x, y = self.grid[0].center
x += self.board_rect.x
y += self.board_rect.y
self.win.blit(self.x_tile_trans, self.x_tile_trans.get_rect(center=(x, y)))
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

Moving surface within smaller surface doesn't show previously hidden components

I'm coding some custom GUI objects for usage in pygame menus, while coding a scrollable box I hit an error.
This box works by moving a surface (which contains the components which are moved when scrolling) within a smaller surface which acts like a window to the confined surface. The surfaces mostly display correctly: the contents of the inner surface which are visible initially (the parts which fit within the window surface) display correctly, but when the inner surface is moved to reveal previously hidden components they are not displayed, the initial visible move correctly and are displayed when they return.
I think the issue is with the outer surface's clipping area thinking that only the already revealed components should be displayed and that the others are still hidden but I don't know.
The custom GUI components always have a Rect (returns the bounding rect for that component) and Draw (blits the component to the screen) functions.
Here is the code for the scroll area (and it's parent class):
class ScrollArea(BaseComponent):
"Implements a section of screen which is operable by scroll wheel"
def __init__(self,surface,rect,colour,components):
"""surface is what this is drawn on
rect is location + size
colour is colour of screen
components is iterable of components to scroll through (they need Draw and Rect functions), this changes the objects location and surface
"""
super().__init__(surface)
self.rect = pygame.Rect(rect)
self.colour = colour
self.components = components
self.Make()
def HandleEvent(self, event):
"Pass events to this; it enables the area to react to them"
if event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos) and self._scroll_rect.h > self.rect.h:
if event.button == 4: self.scroll_y = min(self.scroll_y + 15,self._scroll_y_min)
if event.button == 5: self.scroll_y = max(self.scroll_y - 15,self._scroll_y_max)
def Make(self):
"Updates the area, activates any changes made"
_pos = self.rect.topleft
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self.rect = pygame.Rect(_pos,self._sub_surface.get_rect().size)
self._sub_surface.unlock()#hopefully fixes issues
self._scroll_surf = pygame.Surface(self.rect.size)
self._scroll_rect = self._scroll_surf.get_rect()
scroll_height = 5
for component in self.components:
component.surface = self._scroll_surf
component.Rect().y = scroll_height
component.Rect().x = 5
component.Draw()
scroll_height += component.Rect().h + 5
self._scroll_rect.h = max(self.rect.h,scroll_height)
self.scroll_y = 0
self._scroll_y_min = 0
self._scroll_y_max = -(self._scroll_rect.h - self.rect.h)
def Draw(self):
"Draw the area and its inner components"
self._sub_surface.fill((255, 255, 255, 0))
self._sub_surface.blit(self._scroll_surf,(0,self.scroll_y))
pygame.draw.rect(self._sub_surface,self.colour,((0,0),self.rect.size),2)
self.surface.blit(self._sub_surface,self.rect.topleft)
def Rect(self):
"Return the rect of this component"
return self.rect
class BaseComponent:
def __init__(self,surface):
"surface is what this is drawn on"
self.surface = surface
def HandleEvent(self,event):
"Pass events into this for the component to react ot them"
raise NotImplementedError()
def Make(self):
"Redo calculations on how component looks"
raise NotImplementedError()
def Draw(self):
"Draw component"
raise NotImplementedError()
def ReDraw(self):
"Call Make then draw functions of component"
self.Make()
self.Draw()
def Rect(self):
"Return the rect of this component"
raise NotImplementedError()
To test this I used this code and a label component:
screen_width = 640
screen_height = 480
font_label = pygame.font.Font("freesansbold.ttf",22)
screen = pygame.display.set_mode((screen_width,screen_height))
grey = (125,125,125)
def LoadLoop():
#objects
scroll_components = []
for i in range(20):
scroll_components.append(Components.Label(screen,(0,0),str(i),font_label,grey))
scroll_area = Components.ScrollArea(screen,Components.CenterRect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
clock = pygame.time.Clock()
running = True
while running:
#events
for event in pygame.event.get():
scroll_area.HandleEvent(event)
if event.type == pygame.QUIT:
running = False
pygame.quit()
exit()
#graphics
screen.fill(black)
scroll_area.Draw(components)
#render
pygame.display.update()
clock.tick(60)
This is the label component's code (it basically just prints text to screen with the location given as it's center):
class Label(BaseComponent):
"Class which implements placing text on a screen"
def __init__(self,surface,center,text,font,text_colour):
"""surface is what this is drawn on
center is the coordinates of where the text is to be located
text is the text of the label
font is the font of the label
text_colour is the text's colour
"""
super().__init__(surface)
self.center = center
self.text = text
self.font = font
self.text_colour = text_colour
self.Make()
def HandleEvent(self,event):
"Labels have no events they react to,\nso this does nothing"
def Make(self):
"(Re)creates the label which is drawn,\nthis must be used if any changes to the label are to be carried out"
self._text_surf = self.font.render(self.text, True, self.text_colour)
self._text_rect = self._text_surf.get_rect()
self._text_rect.center = self.center
def Draw(self):
"Draw the label , will not react to any changes made to the label"
self.surface.blit(self._text_surf,self._text_rect)
def Rect(self):
"Return the rect of this component"
return self._text_rect
This is the window produced by this code:
Before scrolling
After scrolling
I also did it with a different size of ScrollArea, one of the Labels was positioned through the bottom and it was cut in half, when scrolled the cut remained.
Please help.
Sidenote on conventions
First, a sidenote on conventions: class names should start with an uppercase letter, function and method names should be all lowercase.
They are conventions, so you are free to not follow them, but following the conventions will make your code more readable to people used to them.
The quick fix
The error is in the ScrollArea.Make() method. Look carefully at these two lines:
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self._scroll_surf = pygame.Surface(self.rect.size)
self._sub_surface is the surface of the window of the scroll area. self._scroll_surf is the scrolling surface. The latter should be higher, but you set them to the same size (same width is fine, same height not).
Obviously when you loop over your component list to blit the Label, the ones which are outside self._sub_surface are also outside self._scroll_surf and hence are not blit at all. You should make self._scroll_surf higher. Try for example:
self._scroll_surf = pygame.Surface((self.rect.width, self.rect.height*10)
Better would be to estimate the proper height to contains all your labels, which should be scroll_height, but you calculate it later in the method, so you should figure how to do properly this part.
A general advice
In general, I think you have a design problem here:
for i in range(20):
scroll_components.append(Label(screen,(0,0),str(i),font_label,grey))
scroll_area = ScrollArea(screen, pygame.Rect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
When you create each label, you pass the screen as the reference surface where the Draw method blits.
But these labels should be blitted on the scroll_surf of your ScrollArea. But you cannot do it because you have not instantiated yet the ScrollArea, and you cannot instantiate before the scroll area because you require the Labels to be passed as an argument.
And in fact in the ScrollArea.Make() method you overwrite each label surface attribute with the _scroll_surf Surface.
I think would be better to pass to ScrollArea a list of strings, and let the ScrollArea.__init__() method to create the labels.
It will look less patched and more coherent.

pyglet - loading/blitting image with alpha

I'm trying to make a simple application with pyglet. My main problem so far is that I can't seem to blit an image with alpha - all of the transparent pixels are converted into black pixels. I'm not sure whether the problem is with the loading of the image or the blitting. Here is a very basic overview of how I'm trying to render the image:
import pyglet
import pyglet.clock
window = pyglet.window.Window()
window.config.alpha_size = 8
#fancy text
text = pyglet.resource.image("text.png")
#background image
bg = pyglet.resource.image("bg.png")
bg.blit(0, 0)
text.blit(100, 100)
pyglet.app.run()
Any help is appreciated. Thanks in advance.
You most likely just need to enable GL ALPHA blends.
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
But first of all, your code is not able to run.
Mostly because you don't declare a window.event function to handle the on_draw where you normally render things.
Secondly, you never clear your window (which will cause a mess).
Here's a minimal working example of your code:
import pyglet
import pyglet.clock
window = pyglet.window.Window()
window.config.alpha_size = 8
#fancy text
text = pyglet.resource.image("text.png")
#background image
bg = pyglet.resource.image("bg.png")
#window.event
def on_draw():
window.clear()
bg.blit(0, 0)
text.blit(100, 100)
pyglet.app.run()
Now this generates this:
And here's a working example of how you use the GL_BLEND feature:
import pyglet
import pyglet.clock
from pyglet.gl import *
window = pyglet.window.Window()
window.config.alpha_size = 8
#fancy text
text = pyglet.resource.image("text.png")
#background image
bg = pyglet.resource.image("bg.png")
#window.event
def on_draw():
window.clear()
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
bg.blit(0, 0)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
text.blit(100, 100)
pyglet.app.run()
This yields a result like so:
However, this code will quickly become messy.
So there's two things you can do. You can first, put your images into sprite objects. Secondly, make this a bit more object oriented.
First, we'll use sprites.
self.fancy_background = pyglet.sprite.Sprite(pyglet.image.load('bg.png'))
self.fancy_background.draw() # not blit!
Sprites automatically uses transparency, which makes your life (and code) a lot easier.
Secondly, we'll put these into a batch.
Batches are made to bunch A LOT of sprites so you can call .draw() on the batch, and all sprites in that batch gets insta-rendered.
self.background = pyglet.graphics.Batch()
self.fancy_background = pyglet.sprite.Sprite(pyglet.image.load('bg.png'), batch=self.background)
self.background.draw() # background, not fancy_background! And also not blit!!
Last and most certainly not least.
We'll put this into a class so we can do cool stuff later on.
import pyglet
import pyglet.clock
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.background = pyglet.graphics.Batch()
self.texts = pyglet.graphics.Batch()
self.fancy_background = pyglet.sprite.Sprite(pyglet.image.load('bg.png'), batch=self.background)
self.fancy_text = pyglet.sprite.Sprite(pyglet.image.load('text.png'), batch=self.texts)
self.mouse_x = 0
self.mouse_y = 0
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_motion(self, x, y, dx, dy):
self.mouse_x = x
self.mouse_y = y
def on_mouse_press(self, x, y, button, modifiers):
if button == 1: # Left click
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.background.draw()
self.texts.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
BAM.
This code will enable you to create custom functions and custom "player objects" later on for instance. Also you can do collision detection easier and the code just looks a lot more structured (I threw in a little bonus features such as keyboard and mouse events).
Note tho, that the position of the sprites will default to x=0, y=0 as shown in the last picture. You can set the position with x=100 either on the variable/handle or when creating the sprite.

Categories

Resources