How to scale my Tkinter program across different PPIs and screen resolutions - python

I have been developing an app for someone and we quickly realized that the app looks different between our laptops (and on my tablet its un-usable because of the ppi). I looked up what the problem was so I used starting_frame.tk.call('tk', 'scaling', factor) to normalize the PPI across all devices. What I quickly realized, however, was the program will still be too big or too small depending on the screen ( i have a resolution set at root.geometry("1280x960") . In order to combat all of these problems, I created the code below:
root = tk.Tk()
dpi = root.winfo_fpixels('1i')
factor = dpi / 72
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
ratio_1 = width * height
ratio_2 = 1
r2_width = 4
r2_height = 3
while (ratio_1 / ratio_2) != 1.6875:
r2_height = r2_width / 1.33333333333
ratio_2 = r2_width * r2_height
r2_width = r2_width + 1
if (ratio_1/ratio_2) <= 1.6875:
break
if width + 1 == r2_width:
break
root.geometry(str(r2_width) + "x"+ str(int(r2_height)))
starting_frame = tk.Canvas(root)
factor_multiplier = (.40*factor) +.46
factor = factor/factor_multiplier
starting_frame.tk.call('tk', 'scaling', factor)
starting_frame.place(height=int(r2_height), width = r2_width)
Let me break this down:
dpi = root.winfo_fpixels('1i')
factor = dpi / 72
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
this just grabs the ppi of the device and the screen res....
ratio_1 = width * height
ratio_2 = 1
r2_width = 4
r2_height = 3
while (ratio_1 / ratio_2) != 1.6875:
r2_height = r2_width / 1.33333333333
ratio_2 = r2_width * r2_height
r2_width = r2_width + 1
if (ratio_1/ratio_2) <= 1.6875:
break
if width + 1 == r2_width:
break
root.geometry(str(r2_width) + "x"+ str(int(r2_height)))
This basically takes my 16x9 display and finds a 4:3 resolution that is 88% or so the area of my screen res (i just think its a good size and is that my program is based around).
factor_multiplier = (.40*factor) +.46
factor = factor/factor_multiplier
this converts the ppi of any screen so that the size of the text and stuff is normalized across displays (I assumed a linear relation).
starting_frame.tk.call('tk', 'scaling', factor)
starting_frame.place(height=int(r2_height), width = r2_width)
this is just making a frame and stuff based on my calculated new ppi and res.
While this sorta works, and then all of my hard coded text positions are multiplied by my factor_multiplier , it is a very sloppy and long way of doing this. please tell me there is a better way because I have been looking and I can't find anything that suits my needs.

You can the ctypes Python library. This following setting in the ctypes library sets DPI awareness.
import ctypes
 
ctypes.windll.shcore.SetProcessDpiAwareness(1)
 

Related

How to have an auto page break for pyfpdf when the image exceeds the page dimensions?

I looked at a similar question here but there doesn't seem to be a solution except to switch to a different library.
I have created some code to automatically space my images vertically and horizontally, but I am having a problem with auto page breaks when the image inserted is larger than the page (since the image will get cut off).
def generate_pdf_report(start_height):
name = "PDF"
page_width = 210
page_height = 297
object_margins = 10
objects_per_row = 1
objects_in_path = 6 ##SAMPLE VARIABLE
total_margins = object_margins * (objects_per_row + 1)
object_width = (page_width - total_margins)/objects_per_row
pdf = FPDF()
pdf.add_page()
pdf.set_font('Arial', 'B', 16)
object_counter = 0
height_position = start_height
for item in range(objects_in_path):
object_counter += 1
position = object_counter % objects_per_row
if position == 0:
start_position = object_width * (objects_per_row - 1) + object_margins * objects_per_row
pdf.image("test_image.jpg", x=start_position, y=height_position, w=object_width)
height_position += start_height + object_margins
else:
start_position = object_width * (position - 1) + object_margins * position
pdf.image("test_image.jpg", x=start_position, y=height_position, w=object_width)
pdf.output('test_pdf.pdf')
Is there some kind of way to return the dimensions of each image inserted using pdf.image()? Since I set the width of the image, the height is automatically scaled by default. So currently I have to actually manually set the start_height parameter of the function depending on the resulting height of the image--else it can overlap or have too large a gap. Since I don't have the image height to adequately space the images or skip to the next page
There is one solution I can think of which is to measure the image outside the pyfpdf package and determine the width-height ratio of the image so I can determine the height of the final image. But I think there should be a better and easier way?

Cutting the codes in the program and making it neater

I'm having a hard time cutting the code and making it into a loop so that it would make the code of the program, neater.
Although my code works as it suppose to be, I think there is a right way of creating it, adding a for loop rather than writing all of these codes, I know there is an easy way to do this, I just couldn't figure how to do it properly. I know I'm suppose to create a for loop.
squares
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
rect = Rectangle(Point(0,500), Point(500,0))
rect.setFill("Red")
rect.draw(win)
rect2 = Rectangle(Point(20,480), Point(480,20))
rect2.setFill("white")
rect2.draw(win)
rect3 = Rectangle(Point(40,460), Point(460,40))
rect3.setFill("red")
rect3.draw(win)
rect4 = Rectangle(Point(60,440), Point(440,60))
rect4.setFill("white")
rect4.draw(win)
rect5 = Rectangle(Point(80,420), Point(420,80))
rect5.setFill("red")
rect5.draw(win)
rect6 = Rectangle(Point(100,400), Point(400,100))
rect6.setFill("white")
rect6.draw(win)
rect7 = Rectangle(Point(120,380), Point(380,120))
rect7.setFill("red")
rect7.draw(win)
rect8 = Rectangle(Point(140,360), Point(360,140))
rect8.setFill("white")
rect8.draw(win)
rect9 = Rectangle(Point(160,340), Point(340,160))
rect9.setFill("red")
rect9.draw(win)
rect10 = Rectangle(Point(180,320), Point(320,180))
rect10.setFill("white")
rect10.draw(win)
rect11 = Rectangle(Point(200,300), Point(300,200))
rect11.setFill("red")
rect11.draw(win)
rect12 = Rectangle(Point(220,280), Point(280,220))
rect12.setFill("white")
rect12.draw(win)
The results shows squares into some sort of a patchwork
Try the following:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
# create all rects
rects = [Rectangle(Point(0 + 20*i,500 - 20*i), Point(500 - 20*i, 0 + 20*i)) for i in range(12)]
# draw all rects
for idx, rect in enumerate(rects):
rect.fill("red" if idx % 2 == 0 else "white")
rect.draw(win)
If the patchwork is just a background and you don't plan on modifying it you could use this:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
i = 1
for x in range(0, 221, 20):
rect = Rectangle(Point(x, 500 - x), Point(500 - x,x))
rect.setFill("red" if i % 2 else "white")
rect.draw(win)
i += 1
An alternate approach that only needs to draw half as many rectangles due to using the rectangle's outline as the other color:
SQUARE, WIDTH = 500, 20
def main():
win = GraphWin("Squares", SQUARE, SQUARE)
save_config = dict(DEFAULT_CONFIG)
DEFAULT_CONFIG.update(dict(outline='red', fill='white', width=WIDTH))
for xy in range(WIDTH//2, SQUARE//2, WIDTH*2):
Rectangle(Point(xy, SQUARE - xy), Point(SQUARE - xy, xy)).draw(win)
DEFAULT_CONFIG.update(save_config)
It's fully parameterized so you can fit it to a different size square or have different width stripes by adjusting the SQUARE and WIDTH parameters. Rather than draw 12 rectangles in alternating colors, with the parameters as currently set, it draws 6 white rectangles with red outlines:

A box within a box

I am attempting to solve a problem where I have a region of size = width * height and then have to find a sub-region within the region at a location x, y and of size width_1 * height_1.
The values are:
Region_width = 71680
Region_height = 39680
Sub_location_x=272
Sub_location_y=130
Sub_width=372
Sub_height=519
The code I am attempting to implement I think should be like this.
#Width height of box
region_width = 71680
region_height = 39680
x = 272
y = 130
sub_width = 372
sub_height = 519
#Create numpy array of width and height (This I am unsure of)
Region_array = np.array(width, height)
#Pull out sub box (This I am also unsure of)
Sub_box = Region_array[x: x + sub_width, y: y + sub_height]
Would really appreciate any help or insight has to how I solve the problem.

QR Code manipulation using Python (jython) - Scaling and removing Quiet Zone

Having trouble with this particular task (using JES 4.3). The task is to scale down a QR code image using Python to a 25 x 25 image, and to remove the white border (quiet zone) from the image. I have no trouble scaling it down, but the removal of the quiet zone is troubling. I believe it must be done before the scaling itself, and the quiet zone width can vary. Thus it appears that I need to remove each outer layer of pixels one by one until the program detects that its no longer the quiet zone. Quite sure a while loop is required, but have no clue how to implement that effectively. My code so far:
def reduce(qrPicture):
file = makePicture(qrPicture)
myPicture = duplicatePicture(file)
width = getWidth(myPicture)
height = getHeight(myPicture)
newPicture = makeEmptyPicture(25, 25)
newWidth = getWidth(newPicture)
newHeight = getHeight(newPicture)
basePixel = getPixelAt(myPicture, 0, 0)
startX = 0
startY = 0
xWidth = width/float(newWidth)
yHeight = height/float(newHeight)
for x in range(startX, newWidth):
for y in range(startY, newHeight):
smallPix = getPixel(newPicture, x, y)
pixelX = x*xWidth;
pixelY = y*yHeight;
oPixel = getPixel(myPicture, int(pixelX), int(pixelY))
setColor(smallPix, getColor(oPixel))
As mentioned, this simply scales the image to 25 x 25 but does not remove the quiet zone. Can someone tell me how to implement this removal of the quiet zone? Thanks.
def findQuietZone(pic):
width = getWidth(pic)
height = getHeight(pic)
for x in range(0, width):
for y in range(0, height):
px = getPixel(pic, x, y)
color = getColor(px)
if (colour != white):
value = getX(px)
quietZone = width - value

Randomly orientated lines drawn off a random point in python

I'm trying to create python program that has several vertical lines which act as boundaries where randomly generated points or "dots" (as referred to in the code) which draw a straight line at a random degree. If the straight line intersects with one of the vertical "boundaries" I want to make it change colour. I have a picture of what I am trying to achieve which will probably explain my situation a bit clearer. The code I post below has drawn the "vertical boundaries" and has the points randomly generated within the region, however that is where I am stuck.
What I am aiming to achieve:
Example of program
My current Code:
setup(750,750)
screen_size = 750
max_coord = (screen_size - 30) / 2
### change the number of dots you have via that variable
num_dots = 500
bgcolor('yellow')
dot_size=5
reset() # Create an empty window
pi = Turtle()
hideturtle()
def parallel_lines(number):
pi.pensize(2)
pi.pencolor('black')
width = pi.window_width()
height = pi.window_height()
pi.setheading(90)
pi.penup()
pi.setposition(width/-2, height/-2)
for i in range(1, number +2):
pi.pendown()
pi.forward(height)
pi.penup()
pi.setposition(width/-2+i*(width/(number+1)),height/-2)
parallel_lines(7)
## centre turtle back in the middle of the page
goto(0,0)
### list to hold the dots
x_coords = []
y_coords = []
### Draw the dots via randomint
penup()
color("blue")
for dot_num in range(num_dots):
dot_pos_x = randint (-max_coord, max_coord)
dot_pos_y = randint (-max_coord, max_coord)
goto(dot_pos_x, dot_pos_y)
dot(dot_size)
x_coords.append(dot_pos_x)
y_coords.append(dot_pos_y)
done()
Thank you in advance for anyone that can help.
Here's an implementation of the program the OP describes. If there is a line intersection, it uses Python 3 turtle's undo feature to remove the line and redraw it in the alternate color:
from turtle import Turtle, Screen
from random import randint, randrange
SCREEN_SIZE = 750
PLANK_COUNT = 8
PINHEAD_SIZE = 5
FLOOR_COLOR = "yellow"
DEFAULT_COLOR = "blue"
CROSSING_COLOR = "red"
screen = Screen()
screen.setup(SCREEN_SIZE, SCREEN_SIZE)
screen.bgcolor(FLOOR_COLOR)
# configure numbers to replicate Lazzarini's setup
NUMBER_PINS = 3408
PIN_LENGTH = 78.125
PLANK_WIDTH = screen.window_width() / PLANK_COUNT
def parallel_lines(turtle, width, height):
turtle.penup()
turtle.setheading(90)
turtle.sety(height / -2)
x_coordinates = []
for i in range(PLANK_COUNT + 1):
x = i * PLANK_WIDTH - width / 2
turtle.setx(x)
turtle.pendown()
turtle.forward(height)
turtle.penup()
turtle.left(180)
x_coordinates.append(x)
return x_coordinates
pi = Turtle(visible=False)
pi.speed("fastest")
x_coordinates = parallel_lines(pi, screen.window_width(), screen.window_height())
def crosses(x0, x1, coordinates):
for coordinate in coordinates:
if x0 <= coordinate <= x1 or x1 <= coordinate <= x0:
return True
return False
previous_crossings = crossings = 0
max_coord = screen.window_width() / 2
for pin in range(NUMBER_PINS):
x0, y0 = randint(-max_coord, max_coord), randint(-max_coord, max_coord)
pi.color(DEFAULT_COLOR)
pi.goto(x0, y0)
pi.dot(PINHEAD_SIZE)
pi.setheading(randrange(360))
pi.pendown()
pi.forward(PIN_LENGTH)
if crosses(x0, pi.xcor(), x_coordinates):
pi.undo()
pi.color(CROSSING_COLOR)
pi.dot(PINHEAD_SIZE)
pi.forward(PIN_LENGTH)
crossings += 1
pi.penup()
if previous_crossings != crossings:
estimate = (2 * PIN_LENGTH * pin) / (PLANK_WIDTH * crossings)
print(estimate)
previous_crossings = crossings
screen.exitonclick()
Now for the rest of the story. What the OP didn't mention is that this is a drawing of planks in a floor, and we're dropping pins onto it, keeping track of how many cross lines in the flooring, as a means of estimating the value of PI (π)!
Read about Buffon's needle for details. The gist is the probability of a pin crossing a line is a function of PI so we can turn the equation around, drop actual (or virtual) pins, to estimate PI.
The program outputs the running estimate for PI (π), based on pins dropped so far, to the console:
...
3.121212121212121
3.1215970961887476
3.1370772946859904
3.134418324291742
3.131768953068592
3.1381381381381384
3.1384892086330933
3.1358467983243568
3.1451612903225805
3.1454979129397733
3.1458333333333335
3.1491384432560903
3.1465005931198102
3.1438721136767316
3.144208037825059
3.144542772861357
3.1419316843345113

Categories