Forum Archive

how to draw a sketch into an image (not fullscreen)?[SOLVED]

jmv38

Starting from @omz sketch example, i have modified it (see below) to:
- have a smaller view.
- not start in 0,0.

My goal is to have several distinct areas on the screen where i can draw into, not just 1big area.

# SketchView
# this class provides a container for hand drawing

import ui

# The PathView class is responsible for tracking
# touches and drawing the current stroke.
# It is used by SketchView.

class PathView (ui.View):
    def __init__(self, frame):
        self.frame = frame
        self.path = None

    def touch_began(self, touch):
        x, y = touch.location
        self.path = ui.Path()
        self.path.line_width = 8
        self.path.line_join_style = ui.LINE_JOIN_ROUND
        self.path.line_cap_style = ui.LINE_CAP_ROUND
        self.path.move_to(x, y)

    def touch_moved(self, touch):
        x, y = touch.location
        self.path.line_to(x, y)
        self.set_needs_display()

    def touch_ended(self, touch):
        # Send the current path to the SketchView:
        if callable(self.onTouchEnded):
            self.onTouchEnded(self)
        # Clear the view (the path has now been rendered
        # into the SketchView's image view):
        self.path = None

    def draw(self):
        if self.path:
            self.path.stroke()

class SketchView (ui.View):
    def __init__(self, frame, background_color='white'):
        self.background_color = background_color
        self.frame = frame
        # the image manager
        iv = ui.ImageView(frame=self.bounds)
        self.image_view = iv
        self.add_subview(iv)
        # the path manager
        pv = PathView(frame=self.bounds)
        pv.onTouchEnded = self.savePathToImage
        self.add_subview(pv)

    def savePathToImage(self, sender):
        path = sender.path
        old_img = self.image_view.image
        width, height = self.image_view.width, self.image_view.height
        with ui.ImageContext(width, height) as ctx:
            if old_img:
                old_img.draw()
            path.stroke()
            self.image_view.image = ctx.get_image()

# you must have a global view to embed the sketch views
gv = ui.View(background_color='white')
gv.present('fullscreen')

# now creat 1 sketch view
sv = SketchView(frame=(100,100,512,512), background_color='pink')
gv.add_subview(sv)

# and a second one
sv2 = SketchView(frame=(700, 100, 300, 300), background_color='cyan')
gv.add_subview(sv2)
ccc

Inside of SketchView.__init__() you might want to add print(self.bounds, self.frame) to ensure that you are consistently using the right one. If you change to pv = PathView(frame=self.frame) # was self.bounds that solves some of your issues.

My sense is that ui.Path.add_clip() is the way to get rid of out-of-bounds drawing but graphics is not my thing so I might have this wrong.

jmv38

@ccc thanks for your answer.
I have started getting things better with bounds and frame, but still some pb i dont understand. I have to study carefully these properties before using them.
Path.add_clip i tried but didnt work.

I'll come back with a solution some day, unless someone solves it for me first.

JonB

Touch.moved continues to fire even if outside the bounds of the original view.
So, you need to check whether the current x,y is in bounds before adding to the path.

def hit(self,location):
    if location[0]<0 or location[1]<0 or location[0]>self.width or location[1]>self.height:
        return False
    else :
        return True

def touch_moved(self, touch):
    if self.hit(touch.location):
         # do stuff

Note if you leave the view, then come around to the other side, you might get an undesirable jump. One option would be to have a drawing flag which gets set to false when moving out of bounds or touch_end, then rearmed during touchbegan, or when reentering the bounds. Then, if touchmoved is called and drawing is false, you would use move_to instead of line_to

jmv38

@JonB thanks.
'bounds' can be tricky. Here is some of my results below, now i start to understand why it did not work as expected.
I have learned 3 things not in the doc:
- [1] calling present(fullscreen) changes frame to (0, 64, displayWidth, displayHeight-64)
- [2] it seems that changing the bounds width & height keeps the center of the frame unchanged, so frame x,y is changed.
- [3] changing bounds x,y seems to have no action on frame x,y. But bound x,y has the new value, incoherent with frame x,y...
These facts are important to know.

import ui
import console

# Some experiments with View, frame, bounds

# lets create a top view:
v0 = ui.View()
v0.background_color = 'white'
v0.border_color = 'gray'
v0.border_width = 5
v0.corner_radius = 20
v0.frame = (100,100,300,300)
console.clear()
print('frame={0} bounds={1}').format(v0.frame, v0.bounds)
# frame=(100.0, 100.0, 300.0, 300.0) bounds=(0.0, 0.0, 300.0, 300.0)
# this is ok, but now:

v0.present('fullscreen') 
print('frame={0} bounds={1}').format(v0.frame, v0.bounds)
# frame=(0.0, 64.0, 1024.0, 704.0) bounds=(0.0, 0.0, 1024.0, 704.0) :ref [1]
# calling present changes v0.frame to (0, 64, displayWidth, displayHeight-64) 

# now lets create some child view
v1 = ui.View( frame = (100,100,300,300), background_color = 'white')
v1.border_color = 'pink'
v1.border_width = 3
v1.corner_radius = 10

v2 = ui.View()
v2.frame = (10,10,100,100)
v2.background_color = 'pink'
v2.border_color = 'blue'
v2.border_width = 4
v2.corner_radius = 1

v1.add_subview(v2)
v0.add_subview(v1)

print('frame={0} bounds={1}').format(v2.frame, v2.bounds)
# frame=(10.0, 10.0, 100.0, 100.0) bounds=(0.0, 0.0, 100.0, 100.0) 
# this is ok

v2.bounds=(0, 0, 200, 200)
print('frame={0} bounds={1}').format(v2.frame, v2.bounds)
# frame=(-40.0, -40.0, 200.0, 200.0) bounds=(0.0, 0.0, 200.0, 200.0) : ref [2]
# this was not expected! why is the frame x,y = -40,-40 now?
# it seems that changing the bounds width & height keeps the center of the view unchanged

v2.bounds=(0, 0, 100, 100)
print('frame={0} bounds={1}').format(v2.frame, v2.bounds)
# frame=(10.0, 10.0, 100.0, 100.0) bounds=(0.0, 0.0, 100.0, 100.0)
# ok we are back in place

v2.bounds=(50, 50, 100, 100)
print('frame={0} bounds={1}').format(v2.frame, v2.bounds)
# frame=(10.0, 10.0, 100.0, 100.0) bounds=(50.0, 50.0, 100.0, 100.0) : ref [3]
# changing bounds x,y seems to have no action on frame x,y... why?


jmv38

@JonB i probably dont need to check the bounds. If the path is included in the good subview, it wont get touch events that are outside the subview. The whole thing is to master what are exactly the subview coordinates... But i am getting close...

jmv38

SOLVED.
I have modified the 1rst post code to show the solution. thanks everyone for your help.