as the canvas instructions Stencil and Scissor (which seem to mo me have the same effect) are used to create masks and draw on top of it, im wondering have one could achieve this effect:
Drawing any shape on top of a background image (so far so easy). Then cut text out of the shape in order to look through on the background.
Is there any solution for that? Or any kivy/openGL instructions i have missed?
Here is something that comes close to what you want. It is a brute force method that makes the text of a kivy.core.text.Label transparent and then uses that texture in a canvas Rectangle:
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.texture import Texture
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.core.text import Label as CoreLabel
class MyWidget(FloatLayout):
pass
Builder.load_string('''
<MyWidget>:
Image:
source: 'tester.png'
allow_stretch: True
keep_ratio: True
''')
class TestApp(App):
def build(self):
Clock.schedule_once(self.doit)
return MyWidget()
def doit(self, dt):
# build a Rectangle half the size and centered on the root (MyWidget)
size = (self.root.width/2, self.root.height/2)
pos = (self.root.x + (self.root.width - size[0])/2, self.root.y + (self.root.height - size[1])/2)
with self.root.canvas.after:
Color(1,1,1,1)
self.rect = Rectangle(pos=pos, size=size)
# create a Texture with desired text and desired size
label = CoreLabel(text='Hello', font_name='Roboto-Bold', font_size=300, valign='center', halign='center',
size=self.root.size, color=(1,1,1,1))
label.refresh()
# make the actual text transparent, and assign the new Texture to the Rectangle
self.rect.texture = self.make_text_transparent(label.texture, (255, 0, 0, 255))
def make_text_transparent(self, texture, bg_color):
# bg_color is a 4 tuple of byte values in the range 0-255
# The normal Label texture is transparent except for the text itself
# This code changes the texture to make the text transparent, and everything else
# gets set to bg_color
pixels = list(texture.pixels)
for index in range(3, len(pixels)-4, 4):
if pixels[index] == 0:
# change transparent pixels to the bg_color
pixels[index-3:index+1] = bg_color
else:
# make the text itself transparent
pixels[index] = 0
# create a new texture, blit the new pixels, and return the new texture
new_texture = Texture.create(size=texture.size, colorfmt='rgba')
new_texture.blit_buffer(bytes(pixels), colorfmt='rgba', bufferfmt='ubyte')
new_texture.flip_vertical()
return new_texture
TestApp().run()
You could generalize this by using another Texture, instead of bg_color and setting the transparent Label pixels to the corresponding pixel from the supplied Texture.
Related
At the moment the below code shows a camera layout using laptop webcam just fine - I want to show a rectangle frame within the camera window - user will hold a book aligned to the frame and I need to capture an image of the book i.e capture image of the part within the frame. I am struggling to
Show a transparent rectangle as a frame (this is inside a boxlayout inside a camera floatlayout)
Grab an image of only the part within the frame
There is a button below the camera layout at click of which the image will be saved to a folder on the machine
Please can somebody guide me how to proceed and whether this can be achieved in any other way using any other module
import kivy
from PIL import ImageGrab
from kivy.uix.boxlayout import BoxLayout
from numpy import shape
kivy.require('1.7.2')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.camera import Camera
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle
class CamApp(App):
# Function to take a screenshot
def screengrab(self, *largs):
im2 = ImageGrab.grab(bbox=None)
im2.show()
#outname = self.fileprefix + '_%(counter)04d.png'
#Window.screenshot(name=outname)
def build(self):
# create a floating layout as base
camlayout = FloatLayout(size=(600, 600))
cam = Camera() # Get the camera
cam = Camera(resolution=(1024, 1024), size=(300, 300))
cam.play = True # Start the camera
camlayout.add_widget(cam)
boxlayout = BoxLayout(id='imageBox', size_hint=[0.5, 0.7], pos_hint={'center_x': .5, 'center_y': .5})
boxlayout.bind(size=self.update_rect, pos=self.update_rect)
with boxlayout.canvas.before:
#Color(0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
self.rect = Rectangle(size=boxlayout.size,
pos=boxlayout.pos, outline='black')
camlayout.add_widget(boxlayout)
button = Button(text='Take Picture', size_hint=(0.12, 0.12))
button.bind(on_press=self.screengrab)
camlayout.add_widget(button) # Add button to Camera Layout
self.fileprefix = 'snap'
return camlayout
def update_rect(self,instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
if __name__ == '__main__':
CamApp().run()
Click to see output
In kivy, the Color() function takes arguments representing RGBA. That means the last argument is alpha, and passing it a float will make it transparent.
So while Color(0, 1, 0, 1) is a solid color, Color(0, 1, 0, .5) will be semitransparent.
As for grabbing an image of only part of the frame, you might consider grabbing the whole frame, then using PIL to crop the image with
from PIL import Image
im = Image.open(r"C:\path\to\picture\my_screenshot.png")
im.crop((left, top, right, bottom))
Just replace left, top, right, and bottom with the dimensions you want to cut out.
I have used to create a Texture with gradient color and set to the background of Label, Button and etc. But I am wondering how to set this to color of Label?
Sounded like a fun puzzle. So here is what I came up with:
from kivy.lang import Builder
from kivy.graphics.texture import Texture
from kivy.properties import ObjectProperty, ListProperty
from kivy.uix.label import Label
class LabelGradient(Label):
gradient = ObjectProperty(None)
bg_color = ListProperty([0, 0, 0, 255])
def __init__(self, **kwargs):
super(LabelGradient, self).__init__(**kwargs)
# bind to texture to trigger use of gradient
self.bind(texture=self.fix_texture)
def fix_texture(self, instance, texture):
if self.gradient is None:
return
# unbind, so we don't loop
self.unbind(texture=self.fix_texture)
# The normal Label texture is transparent except for the text itself
# This code changes the texture to make the text transparent, and everything else
# gets set to self.bg_color (a property of LabelGradient)
pixels = list(self.texture.pixels)
for index in range(3, len(pixels)-4, 4):
if pixels[index] == 0:
# change transparent pixels to the bg_color
pixels[index-3:index+1] = self.bg_color
else:
# make the text itself transparent
pixels[index] = 0
# create a new texture, blit the new pixels, and apply the new texture
new_texture = Texture.create(size=self.texture.size, colorfmt='rgba')
new_texture.blit_buffer(bytes(pixels), colorfmt='rgba', bufferfmt='ubyte')
new_texture.flip_vertical()
self.texture = new_texture
Builder.load_string('''
<LabelGradient>:
canvas.before:
# draw the gradient below the normal Label Texture
Color:
rgba: 1,1,1,1
Rectangle:
texture: self.gradient
size: self.texture_size
pos: int(self.center_x - self.texture_size[0] / 2.), int(self.center_y - self.texture_size[1] / 2.)
''')
if __name__ == '__main__':
from kivy.app import App
from kivy.lang import Builder
class LabelGradientApp(App):
grad = ObjectProperty(None)
def build(self):
# create a 64x64 texture, defaults to rgba / ubyte
self.grad = Texture.create(size=(64, 64))
# create 64x64 rgb tab, and fill with values from 0 to 255
# we'll have a gradient from black to white
size = 64 * 64 * 3
buf = bytes([int(x * 255 / size) for x in range(size)])
# then blit the buffer
self.grad.blit_buffer(buf, colorfmt='rgb', bufferfmt='ubyte')
return theRoot
app = LabelGradientApp()
theRoot = Builder.load_string('''
FloatLayout:
LabelGradient:
text: 'Abba Dabba Doo'
font_size: 30
gradient: app.grad
''')
app.run()
My approach is to make the text transparent, and to draw a gradient behind the text.
The normal Texture created by the Label is transparent except for the actual text.
The LabelGradient above extends Label and draws the gradient using the <LabelGradient> rule in the kv. The fix_texture() method is bound to the texture property of the Label, so it is called as soon as the Texture is created. The fix_texture() method makes the actual text transparent and sets the rest of the Texture to the bg_color.
You can't set the color property to a gradient, that just isn't what it does. Gradients should be achieved using images or textures directly applied to canvas vertex instructions.
In example below, there are two rectangles drawn in canvas of FloatLayout.
The goal is to create something like a simple pixel art drawing app where the user can draw rectangles and change their color (for example color of rectangle under mouse), so I can’t create these rectangles in kv file.
So in this demo example I just want to change the color of rectangle under the mouse.
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.graphics import Color, Rectangle
KV = """
FloatLayout
size_hint: None, None
size: 512, 512
on_touch_down: app.test(*args[1].pos)
"""
class MyApp(App):
color = ListProperty((1,1,1,1))
def build(self):
self.root = Builder.load_string(KV)
self.init_rects()
def init_rects(self):
with self.root.canvas:
x,y = self.root.pos
w,h = self.root.size
Color(rgba=(1,1,1,1))
self.r1 = Rectangle(pos = (x,y), size= (w/2,h))
Color(rgba=(1,0,0,1))
self.r2 = Rectangle(pos = (w/2,y), size= (w/2,h))
def test(self, x,y):
if x< self.root.center_x:
print ('I need to color this rectangle (self.r1) to red')
else:
print ('I need to color this rectangle (self.r2) to white')
MyApp().run()
In this example I store rectangles as self.r1 and self.r2 (because I think further I will need to change them pos or size)
The problem is I didn't find an example of how to change only one rectangle color, and not to change other colors.
I have a stupid solution (below) - every time to create a new rectangle. But I am sure that this is a bad solution when there will be a lot of rectangles
def test(self, touch_x, touch_y):
with self.root.canvas:
x,y = self.root.pos
w,h = self.root.size
if touch_x< self.root.center_x:
Color(rgba=(1,0,0,1))
self.r1 = Rectangle(pos = (x,y), size= (w/2,h))
else:
Color(rgba=(1,1,1,1))
self.r2 = Rectangle(pos = (w/2,y), size= (w/2,h))
Roughly speaking I miss something like Rectangle(rgba=...)
What could be the solution in this case?
You can change the Color instead of trying to change the Rectangle. Here is a modification of your code that demonstrates this:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.graphics import Color, Rectangle
KV = """
FloatLayout
size_hint: None, None
size: 512, 512
on_touch_down: app.test(*args[1].pos)
"""
class MyApp(App):
color = ListProperty((1,1,1,1))
def build(self):
self.root = Builder.load_string(KV)
self.init_rects()
def init_rects(self):
with self.root.canvas:
x,y = self.root.pos
w,h = self.root.size
self.c1 = Color(rgba=(1,1,1,1))
Rectangle(pos = (x,y), size= (w/2,h))
self.c2 = Color(rgba=(1,0,0,1))
Rectangle(pos = (w/2,y), size= (w/2,h))
def test(self, x,y):
if x< self.root.center_x:
print ('I need to color this rectangle (self.r1) to red')
self.c1.rgba = (1,0,0,1)
else:
print ('I need to color this rectangle (self.r2) to white')
self.c2.rgba = (1,1,1,1)
MyApp().run()
I've started playing with Kivy, but I'm rather new to all this and am already struggling.
I'm trying to make a board game - I want the window to be resizeable, but I don't want resizing the window to mess up the aspect ratio of the game (so in other words, I want the window to have black bars above or to the sides of the content if the window is resized to something other than the intended aspect ratio of the content)
The easiest way I could see to ensure this is to either:
a) Lock the aspect ratio of the Window itself so it's always 10:9 (the aspect ratio of the board and all on-screen elements) - even when fullscreen.
or
b) Use some sort of widget/surface/layout that is centered on the window and has a locked 10:9 aspect ratio. I then subsequently use this as the base onto which all my other images, widgets, grids etc are placed.
However, I really don't know how to do either of these. I'm not sure if I can lock the Window aspect ratio, and the only kivy-specific object I've found that lets me lock the aspect ratio is from kivy.graphics.image... which I don't seem to be able to use as a 'base' for my other stuff.
EDIT: So far I've written the code below: it creates a layout (and colours it slightly red) that 'fixes' its own aspect ratio whenever it is resized. However, it still isn't centered in the window, and more problematic, it results in an endless loop (probably because the aspect fix code corrects the size, but then kivy 'corrects' its size back to being the size of the window, triggering the aspect fix again, thought I could be wrong).
EDIT: Modified the code again, but it's still an endless loop. I though that referring only to the parent for size info would fix that, but apparently not.
I'd appreciate anyone helping me fix my code.
Code below:
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Board(AnchorLayout):
pass
class BackgroundLayout(RelativeLayout):
def FixAspectRatio(self, *args):
correctedsize = self.parent.size
if correctedsize[0] > correctedsize[1]*(10/9):
correctedsize[0] = correctedsize[1]*(10/9)
elif correctedsize[0] < correctedsize[1]*(10/9):
correctedsize[1] = correctedsize[0]/(10/9)
return correctedsize
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Board()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Board>
BackgroundLayout:
canvas.before:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: self.pos
size: self.FixAspectRatio(self.parent.size)
pos: self.parent.pos
One approach would be to create a Layout that always maximize the children given an aspect ratio. Here is an example::
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty
kv = """
ARLayout:
Widget:
canvas:
Color:
rgb: 1, 0, 0
Rectangle:
size: self.size
pos: self.pos
"""
class ARLayout(RelativeLayout):
# maximize the children given the ratio
ratio = NumericProperty(10 / 9.)
def do_layout(self, *args):
for child in self.children:
self.apply_ratio(child)
super(ARLayout, self).do_layout()
def apply_ratio(self, child):
# ensure the child don't have specification we don't want
child.size_hint = None, None
child.pos_hint = {"center_x": .5, "center_y": .5}
# calculate the new size, ensure one axis doesn't go out of the bounds
w, h = self.size
h2 = w * self.ratio
if h2 > self.height:
w = h / self.ratio
else:
h = h2
child.size = w, h
class TestApp(App):
def build(self):
return Builder.load_string(kv)
TestApp().run()
I managed to find a solution I was happy with with a lot of help from kived from the kivy chat.
The below bases the game bounds on a 'background' image, then places a Relative Layout over this background image. For all subsequent children, the coordinate (0, 0) will refer to the bottom left of the (aspect locked) background image, and they can use the 'magnification' property of the Background to adjust their size/position according to the screen size.
Provided any gamebounds.png image, the below code will implement this. Note that the RelativeLayout is shaded red (with 50% transparency) just to show its position (and show that it matches the gamebounds.png image size and position at all times):
test.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import (ObjectProperty,
NumericProperty,
ReferenceListProperty,
ListProperty)
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
class Game(FloatLayout):
pass
class Background(Image):
offset = ListProperty()
magnification = NumericProperty(0)
class Bounds(RelativeLayout):
pass
class test(App):
game = ObjectProperty(None)
def build(self):
self.game = Game()
return self.game
if __name__ == '__main__':
test().run()
test.kv
<Game>
Background:
id: BackgroundId
#Provide the image that will form the aspect-locked bounds of the
#game. Image widgets have aspect_lock set to True by default.
source: 'gamebounds.png'
#All the image to stretch to fill the available window space.
allow_stretch: True
size: self.parent.size
#Find the coordinates of the bottom-left corner of the image from
#the bottom-left corner of the window. Call this the 'offset'.
offset: [self.center_x - (self.norm_image_size[0]/2),
self.center_y - (self.norm_image_size[1]/2)]
#Find out the factor by which the image is magnified/shrunk from
#its 'default' value:
magnification: self.norm_image_size[0] / self.texture_size[0]
Bounds:
#The canvas below isn't needed, it's just used to show the
#position of the RelativeLayout
canvas:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
size: self.size
pos: (0, 0)
#Set the position of the RelativeLayout so it starts at the
#bottom left of the image
pos: self.parent.offset
#Set the size of the RelativeLayout to be equal to the size of
#the image in Background
size: self.parent.norm_image_size
Problem in hand is that i have a screen where i have created 4 widgets under gridlayout . Widget one is a custom widget which have a boxlayout and have 2 images and a button .
and other 3 widgets are simple buttons
Now i want to have a background image or coloured rectangle to the 1st widget but image is getting drawn at position 0,0 . I am trying to use canvas instructions to create a rectangle or Border Image but seems gridlayout shows the widget position as 0,0 and hence it created the rectangle at 0,0 . please see the code below :
Any ideas how to fix this and create rectangle as at the border of widget 1?
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen,FallOutTransition
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import BorderImage
from kivy.graphics import Color, Rectangle ,Line
################# Images ######################
S = Image(source='images/Sti.png')
L = 'images/spinnin.gif'
################# Images ######################
class CButtonW(BoxLayout):
def __init__(self, **kwargs):
print "init --> CButton self.pos is ",self.pos
super(CButtonW, self).__init__(**kwargs)
self.orientation = 'vertical'
with self.canvas.before:
Color(1, 1, 0, 1, mode='rgba')
Rectangle(pos=self.pos,size=self.size)
BorderImage(
border=(10, 10, 10, 10),
source='images/tex.png')
self.add_widget(S)
self.add_widget(Button(text="Button 1"))
self.add_widget(Image(source=L))
class LevelScreen(Screen,GridLayout):
def __init__(self, **kwargs):
super(LevelScreen, self).__init__(**kwargs)
with self.canvas:
Line(points=(10, 10, 20, 30, 40, 50))
Color(1, 0, 1, 1, mode='rgba')
Rectangle(pos=self.pos, size=Window.size)
self.layout = GridLayout(cols=3,spacing=1,padding=10)
self.Button1 = CButtonW(id='1',text='Level 1')
self.Button2 = Button(id='2',text='Level 2')
self.Button3 = Button(id='3',text='Level 3')
self.Button4 = Button(id='4',text='Level 4')
self.layout.add_widget(self.Button1)
self.layout.add_widget(self.Button2)
self.layout.add_widget(self.Button3)
self.layout.add_widget(self.Button4)
LevelScreen.cols=3
LevelScreen.add_widget(self,self.layout)
print "position of 1st button is ",self.Button1.pos
print "position of 2 button is ",self.Button2.pos
# App Class
class MyJBApp(App):
def build(self):
sm = ScreenManager(transition= FallOutTransition())
sm.add_widget(LevelScreen(name='level'))
return sm
if __name__ == '__main__':
MyJBApp().run()
Your canvas instructions are drawn before the CButtonW is positioned by its layout, so at that point it's still in its default position of 0, 0.
You can fix it by adding code to reposition the instructions when the widget's position or size changes. The easiest way is to just use kv language in the first place, which will automatically create the relevant bindings, but you can also bind to pos or size in python, or use the on_propertyname events associated with their changes.