Repeating texture in a Rectangle of a variable size - python

I have a texture that I want to repeat inside a Rectangle drawn by canvas.before. The problem is that I don't know what is going to be the size of the Rectangle (it's used as a background for its widget).
For example, I have a Rectangle that has 48 px height and width 100 - 500 px. I want to fill its content by horizontally repeating a 48x48 texture.
I know and tried setting texture.wrap = 'repeat' and texture.uvsize and it works correctly but only if I know the widget size beforehand. For example, setting uvsize = (3, 1) for a widget with size 144x48 works fine.
However, this doesn't work when I want to update uvsize before redrawing the widget. I created a canvas callback and updated uvsize there but this has no effect for some reason:
...
with self.canvas.before:
self.cb = Callback(self.on_canvas_redraw)
...
def on_canvas_redraw(self, instr):
self.texture.uvsize = (self.width / 48, 1)
So how can I dynamically update uvsize? Or is there a better way to handle widget resize or a better way to this altogether?

Related

Kivy, ScrollView scrolls automatically when contents change size

I have a GridLayout inside of a ScrollView. The GridLayout contains about 25 images. The images are chosen at runtime so their sizes cannot be determined beforehand. These images are loaded asynchronously at different times ( there is at least a 500 millisecond difference between them ).
The problem occurs when the image is loaded and the size of the GridLayout changes. From what I understand, the ScrollView's scroll_y is set to some value relative to the original height of the GridLayout. Then, once the image has loaded the size of the GridLayout changes, but the scroll_y is still relative to the old height. This causes the ScrollView to scroll down by a large amount.
I've tried to rectify this by manually changing the scroll_y to match the new height. I'm using the following equation:
Equation for Finding New Scroll_Y
I'm subracting scroll_y from 1 because a scroll value of 1 is at the very top in kivy. I've reordered this equation to the following and have implemented this in code:
Simplified Equation for Finding New Scroll_Y
This has reduced the problem, and there is a less apparent jittering, but it still scrolls up by 5-10 pixels each time an image is loaded.
This is because I'm calculating the new height for the equation, based on:
the old viewport height of the ScrollView
the old height of the image widget
the new height of the image.
But, this calculated height is slightly bigger than what the actual height turns out to be, causing my adjusted scroll_y to be slightly off. I'm not sure why the actual height is smaller.
I'm not sure where to go from here.
Here is a link to a repository that has a minimal reproducible example. Grid Stuttering Example
I believe the easiest way to do what you want is to extend ScrollView. Here is a class extending ScrollView that does what you want:
class ScrollViewNoStutter(ScrollView):
child_height = NumericProperty(0)
def add_widget(self, widget, index=0):
super(ScrollViewNoStutter, self).add_widget(widget, index=index)
widget.bind(size=self.child_size_changed)
def remove_widget(self, widget):
super(ScrollViewNoStutter, self).remove_widget(widget)
widget.unbind(size=self.child_size_changed)
def child_size_changed(self, child, new_child_size):
if new_child_size[1] > self.size[1]:
# re-calculate scroll_y
# calculate distance between scrollview top and child top (in pixels)
y_dist = (1.0 - self.scroll_y) * (self.child_height - self.height)
# calculate new scroll_y that reproduces the above distance
self.scroll_y = 1.0 - y_dist / (new_child_size[1] - self.height)
self.child_height = new_child_size[1]
Just use this class in place of ScrollView, and you won't need any of those calculations in your App.

Why can't I draw onto two different bitmaps in wxPython?

I am trying to draw a button on a bitmap object. Depending on the y position, it should draw on bitmap1 if the y position is within bmp1's height value, and bitmap2 if it isn't.
For some reason this does not work:
wx.Button(bitmap1 if ypos <= bmp1.GetHeight() else bitmap2, label='Run', id=i, pos=(xpos, ypos))
I can only draw the button on one wx.StaticBitmap image or the panel. The images parents are the panel.
This works fine if I want to switch between the bitmap or onto the panel directly.
What gives?
NOTE:
I managed to work around this using PIL to create a dynamic image large enough to accomodate my generated buttons (a continuous y-size, according to their count and placement), however this idea/code should still be valid.
If I substitute the 'bitmap2' value for the panel, and shift the bitmap2 image drawn on the panel by a bit, then I see that the program draws underneath bitmap2. Why? The image is placed exactly like bitmap1, and bitmap1 has no problems being drawn on it by buttons? :O
I figured out the problem:
The button's parent object should get the ypos according to the parent's dimensions, not on where it is drawn on the frame, like so:
wx.Button(bitmap1 if ypos <= bmp1.GetHeight() else bitmap2, label='Run {i}', id=i, pos=(80, ypos if ypos <= bmp1.GetHeight() else ypos-img_height))
ypos if ypos <= bmp1.GetHeight() else ypos-img_height
Finally!

PyQt Pixmap scaling, keep resolution for later

I am creating a label with a pixmap in a cell of a QTableWidget, and I want to be able to "zoom" in and out of the table. I accomplish this by scaling the pixmap with .scaled(width, height) which works totally fine, however once the pixmap is scaled down I can not scale it back up again and maintain the original resolution. In other words once it has drawn to a smaller size I no longer have those pixels for later use, so when I scale it up I'm re-sampling the image.
How can I make it so that I am able to "zoom" in and then back out again of a pixmap while efficiently maintaining the image resolution? Is scaled() the wrong way to go altogether? I could theoretically just reference the original file each time I zoom in and out and create a fresh pixmap at the desired scale, but that assumes that the files are always accessible (which they may not be in my case).
EDIT:
Okay so based on comments I need to create a new pixmap copy for each scale cycle, now the question is how cleanest to do that and how do I make a new instance of the pixmap without editing the original? Here's a snippet of basically what I have now:
global pixArray
pixArray = []
# pix array
label = QtGui.QLabel()
pic = QtGui.QPixmap(imageFile)
label.setPixmap(pic)
#toss this label into the master array for later
pixArray.append(label)
# apply scaled image
label = pixArray[len(pixArray)-1]
picScaled = label.pixmap()
label.setPixmap(picScaled.scaled(rowWidth, rowHeight))
# so now the user wants to scale everything, rowWidth and rowHeight have changed:
for column in range(self.table.columnCount()):
# resize the cells
self.table.setColumnWidth(column, rowWidth)
self.table.setRowHeight(0, rowHeight)
# resize the pixmap label
item = self.table.cellWidget(0, column)
label = pixArray[column]
pic = label.pixmap()
# at this point I am actually scaling the original pixmap, how to create a copy?
item.setPixmap(pic.scaled(rowWidth, rowHeight))

change size of Frame even if there widget python

hi is there any way to change width and height of widget even if there's widget?
i have code like this
form = Tk()
form.geometry("500x500")
def click():
global frame
frame.config(height = 0 ,width = 0)
frame = LabelFrame(form , text = "vaaja")
frame.place(x = 20 , y = 30)
Label(frame, text ="1").grid(row = 0,column = 0 )
Label(frame, text = "2").grid(row = 1 ,column = 0 )
Button(form , text="Click", command = click).place(x = 200 , y = 200)
form.mainloop()
and when I click the button the size of the frame is the same ( I'cant use grid_forget() for labels and then change the size of frame)
Because you are using place, you have two solutions: you can use place to set the width and height to zero, or you can turn geometry propagation off.
Using place to set the width and height
place allows you to define the width and the height of the placed widget, so in your click function you can do this:
def click():
frame.place_configure(width=0, height=0)
Turning geometry propagation off
A frame is resized to fit its contents by something called "geometry propagation". If you turn this off, you can control the size of the frame with the width and height options of the frame itself. Usually it's better to let Tkinter decide the size for you, but sometimes there's a need to have an explicit size, which is why it's possible to turn geometry propagation off.
Since you are using grid to manage the widgets internal to the frame, you need to use grid_propagate(False) to turn geometry propagation off for that frame:
frame.grid_propagate(False)
By doing so, you're responsible for setting the initial width and height of the widget, though you could leave propagation on to get the initial size, then turn it off with the button click in order to work around that issue.
There's an interesting bug (or feature...) in that if you set the width and height to zero, Tkinter won't redraw the window. At least, it doesn't on the Mac. I don't recall the workaround for that because I never, ever need to set a widget to a zero size, but setting it to 1x1 pixel makes it nearly invisible.

How to scroll a tkinter canvas to an absolute position?

I'm using Python and tkinter. I have a Canvas widget that will display just one image. Most times the image will be larger than the canvas dimensions, but sometimes it will be smaller. Let's just focus on the first case (image larger than canvas).
I want to scroll the canvas to an absolute position that I have already calculated (in pixels). How can I do that?
After trying for around half hour, I got another solution that seems better:
self.canvas.xview_moveto(float(scroll_x+1)/img_width)
self.canvas.yview_moveto(float(scroll_y+1)/img_height)
img_width and img_height are the dimensions of the image. In other words, they are the full scrollable area.
scroll_x and scroll_y are the coordinates of the desired top-left corner.
+1 is a magic value to make it work precisely (but should be applied only if scroll_x/y is non-negative)
Note that the current widget dimension is not needed, only the dimension of the contents.
This solution works very well even if the image is smaller than the widget size (and thus the scroll_x/y can be negative).
EDIT: improved version:
offset_x = +1 if scroll_x >= 0 else 0
offset_y = +1 if scroll_y >= 0 else 0
self.canvas.xview_moveto(float(scroll_x + offset_x)/new_width)
self.canvas.yview_moveto(float(scroll_y + offset_y)/new_height)
This is what I have already done:
# Little hack to scroll by 1-pixel increments.
oldincx = self.canvas["xscrollincrement"]
oldincy = self.canvas["yscrollincrement"]
self.canvas["xscrollincrement"] = 1
self.canvas["yscrollincrement"] = 1
self.canvas.xview_moveto(0.0)
self.canvas.yview_moveto(0.0)
self.canvas.xview_scroll(int(scroll_x)+1, UNITS)
self.canvas.yview_scroll(int(scroll_y)+1, UNITS)
self.canvas["xscrollincrement"] = oldincx
self.canvas["yscrollincrement"] = oldincy
But... As you can see... it's very hackish and ugly. To much workaround for something that should be simple. (plus that magic +1 I was required to add, or it would be off-by-one)
Does anyone else have another better and cleaner solution?
In tkinter you can get the width and height of a PhotoImagefile. You can just call it when you use canvas.create_image
imgrender = PhotoImage(file="something.png")
##Other canvas and scrollbar codes here...
canvas.create_image((imgrender.width()/2),(imgrender.height()/2), image=imgrender)
## The top left corner coordinates is (width/2 , height/2)
I found this solution by using self.canvas.scan_dragto(x, y)
Edit: I develop an interface which can scroll, zoom, and rotate an image. Let's extract from my interface the code.
When I want to save the current position of image I use this:
# 1) Save image position
x0canvas = -self.canvas.canvasx(0)
y0canvas = -self.canvas.canvasy(0)
x0, y0 = self.canvas.coords(text)
ximg = x0
yimg = y0
# 2) Restore image position (for example: after a load)
self.text = canvas.create_text(ximg, yimg, anchor='nw', text='')
self.xyscroll(x0canvas, y0canvas)
# Rotate and zoom image
image = Image.open('fileImg.jpg')
..
imageMod = image.resize(new_size)
if rotate != 0:
imageMod = imageMod.rotate(rotate)
imagetk = ImageTk.PhotoImage(imageMod)
imageid = canvas.create_image(canvas.coords(text), anchor='nw', image=imagetk)

Categories

Resources