PyMuPDF insert image at bottom - python-3.x

I'm trying to read a PDF and insert an image at bottom(Footer) of each page in PDF. I've tried with PyMuPDF library.
Problem: Whatever the Rect (height, width) I give, it doesn't appear in the bottom, image keeps appearing only on the top half of the page (PDF page).
My code:
from fitz import fitz, Rect
doc = fitz.open("test_pdf2.pdf")
def add_footer(pdf):
img = open("footer4.jpg", "rb").read()
rect = Rect(0, 0, 570, 1230)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page._wrapContents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')
Processed_image with footer image in the middle of the page:
https://i.stack.imgur.com/HK9mm.png
Footer image:
https://i.stack.imgur.com/FRQYE.jpg
Please help!
Please let me know if this can be achieved by using any other library.

Some pages internally change the standard PDF geometry (which uses the bottom-left page point as (0, 0)) to something else, and at the same time do not encapsulate this doing properly as they should.
If you suspect this, use PyMuPDF methods to encapsulate the current page /Contents before you insert your items (text, images, etc.).
The easiest way is Page._wrapContents() which is also the friendly way to keep incremental PDF saves small. The MuPDF provided Page.cleanContents() does this and a lot more cleaning stuff, but bloats the incremental save delta, if this is a concern.

After doing little trial and error I was able to figure out the problem. I was missing out on proper dimensions of Rect, I had to give 0.85*h in second parameter.
below is the code:
from fitz import fitz, Rect
doc = fitz.open("test_pdf2.pdf")
w = 595
h = 842
def add_footer(pdf):
img = open("footer4.jpg", "rb").read()
rect = fitz.Rect(0, 0.85*h, w, h)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page._wrapContents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')

use:
wrapContents – Page.wrap_contents()
https://pymupdf.readthedocs.io/en/latest/znames.html?highlight=wrapContents
from fitz import fitz, Rect
doc = fitz.open("example.pdf")
w = 250
h = 400
def add_footer(pdf):
img = open("firmaJG.png", "rb").read()
rect = fitz.Rect(0, 0.85*h, w, h)
for i in range(0, pdf.pageCount):
page = pdf[i]
if not page._isWrapped:
page.wrap_contents()
page.insertImage(rect, stream=img)
add_footer(doc)
doc.save('test_pdf5.pdf')

Related

lazy pop up image loading for ipyleaflet

I have a lot of images from different locations.
I want to show them on a ipyleaflet map in a jupyter notebook.
Each image should have a marker, on click of the marker the image should be shown.
Since there are a lot of images i want the contents of the popup to be loaded in a lazy manner (as in, picture will only be loaded into memory when the marker is clicked and after it should be deleted again).
There must be some callback function i could hijack to load the image into memory only on click, but i cannot find it by the life of me. Hope to find some suggestions here.
The on_click method accepts a callback, but does not have access to the Marker object itself, such that i cannot change the popup itself.
Overwriting the `:
class LazyMarker(Marker):
def __init__(self, image_path, **kwargs):
self._image_path = image_path
super(LazyMarker, self).__init__(**kwargs)
def _handle_mouse_events(self, _, content, buffers):
self.popup = HTML()
self.popup.value = "lazy image loading test:" + load_image_function(self.path)
# rest of the original function
event_type = content.get('type', '')
if event_type == 'click':
self._click_callbacks(**content)
if event_type == 'dblclick':
self._dblclick_callbacks(**content)
[...]
seems also not to work. I'm very grateful for some suggestions! Thanks!
Answering my own question like here:
https://github.com/jupyter-widgets/ipyleaflet/issues/1092
m=Map(..)
def callback_with_popup_creation(fname):
def f(**kwargs):
marker_center = kwargs['coordinates']
html = HTML()
html.value="<img class=\"awesome image\" src=\"" + str(img_file_relative) + "\">"
popup = Popup(
location=marker_center,
child=html
)
m.add_layer(popup)
return f
for fname, lat, lon in data:
marker = Marker(location = (lat, lon))
marker.on_click(callback(fname))
hopefully that helps anyone with a similar issue.

Embedding Images and matplotlib Plots together on on tk canvas

A few years ago I wrote a script in python2 that would pull images and generate plots and show them together in one gui, following this old documentation. However, I'm trying to update this to python3, and I've run into an issue with the way that plots are put on tk windows in the latest version of matplotlib. There are updated guides for how to embed matplotlib graphs in tk windows, of course, but these don't seem to be exactly what I want, since I want to plot various images and graphs together.
Currently:
# Start by creating the GUI as root
root=Tk()
root.wm_title("JADESView")
# Create the canvas
canvas=Canvas(root, height=canvasheight, width=canvaswidth)
# Plot the EAZY SED
#image = Image.open(EAZY_files+str(current_id)+"_EAZY_SED.png")
image = getEAZYimage(current_id)
# Crop out the thumbnails
image = cropEAZY(image)
photo = resizeimage(image)
item4 = canvas.create_image(eazy_positionx, eazy_positiony, image=photo)
Label(root, text="EAZY FIT", font=('helvetica', int(textsizevalue*1.5))).place(x=eazytext_positionx, y = eazytext_positiony)
# Plot the BEAGLE SED
#new_image = Image.open(BEAGLE_files+str(current_id)+"_BEAGLE_SED.png")
new_image = getBEAGLEimage(current_id)
new_photo = resizeimage(new_image)
item5 = canvas.create_image(beagle_positionx, beagle_positiony, image=new_photo)
Label(root, text="BEAGLE FIT", font=('helvetica', int(textsizevalue*1.5))).place(x=beagletext_positionx, y = beagletext_positiony)
canvas.pack(side = TOP, expand=True, fill=BOTH)
# Plot the thumbnails
fig_photo_objects = np.empty(0, dtype = 'object')
fig_photo_objects = create_thumbnails(canvas, fig_photo_objects, current_id, current_index, defaultstretch)
I create a tk.canvas object, and then use canvas.create_image to place two PIL photo objects, with labels (the "EAZY SED" and BEAGLE SED"), and this still works with python3. However, the create_thumbnails function I've written creates individual figure objects and then calls a function "draw_figure" to embed them, which I'll post below, but it's from the tk example I linked above:
def draw_figure(canvas, figure, loc=(0, 0)):
""" Draw a matplotlib figure onto a Tk canvas
loc: location of top-left corner of figure on canvas in pixels.
Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
"""
figure_canvas_agg = FigureCanvasAgg(figure)
figure_canvas_agg.draw()
figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
figure_w, figure_h = int(figure_w), int(figure_h)
photo = PhotoImage(master=canvas, width=figure_w, height=figure_h)
# Position: convert from top-left anchor to center anchor
canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo)
# Unfortunately, there's no accessor for the pointer to the native renderer
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
# Return a handle which contains a reference to the photo object
# which must be kept live or else the picture disappears
return photo
And this breaks in all sorts of ways.
I'm open to overhauling how everything is done, but I don't see examples that show how this sort of thing might work. The examples for the latest version of matplotlib all explain that you should use FigureCanvasTkAgg, but I don't exactly know how to use this if I already have an existing canvas with photos embedded.
Any help would be appreciated, and I can also explain more if necessary!

Picture zooming doesnt select the right area

I have an application that displays images. Since people need to read some information out of the image i implemented a zoom functionality.
I set the picture Widget to 600x600. To Preserve the aspect ratio i then scale the picture and draw it to the widget. This works really well.
For the zoom functionality the user should click anywhere on the picture and it should cut out the are 150x150 pixesl around where the cursor clicks. To be precises the click of the cursore should mark the middle of the rectangle i cut out. So if i click on x=300 y=300 the area should be x=225 y=225 width=150 height=150.
To Archive that i scale the coordinates where the user clicks back to the original image resolution, cut out the subimage and scale it back down. Cutting out the scaled image allready loaded in my programm would yield a far worse quality.
The error is simple. The area cut out is not exactly the aerea i would like to cut out. Sometimes it is to far left. Sometimes to far right. Somtes to high sometimes to low... And i fail to see where the problem lies.
I wrote a barebone prototype whith just the functionality needed. After you put in a path to a jpeg picture you should be able to run it.
# -*- coding: utf-8 -*-
"""
Created on Sun Jan 12 12:22:25 2020
#author: Paddy
"""
import wx
class ImageTest(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
self.frame = wx.Frame(None, title='Radsteuer Eintreiber')
self.panelleft = wx.Panel(self.frame)
self.picturepresent=False
self.index=0
self.PhotoMaxSize = 600
self.zoomed=False
#Change path here
self.imagepath='F:\Geolocation\Test\IMG_20191113_174257.jpg'
self.createUI()
self.frame.Centre()
self.frame.Show()
self.frame.Raise()
self.onView()
#Creates UI Elements on Initiation
def createUI(self):
#instructions = 'Bild'
img = wx.Image(self.PhotoMaxSize,self.PhotoMaxSize,clear=True)
self.imageCtrl = wx.StaticBitmap(self.panelleft, wx.ID_ANY,
wx.Bitmap(img),size=(self.PhotoMaxSize,self.PhotoMaxSize))
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.imageCtrl.Bind(wx.EVT_LEFT_UP, self.onImageClick)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL|wx.ALIGN_CENTER, 5)
self.panelleft.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panelleft.Layout()
def onImageClick(self,event):
if self.zoomed:
self.onView()
self.zoomed=False
else :
#Determin position of mouse
ctrl_pos = event.GetPosition()
print(ctrl_pos)
picturecutof=self.PhotoMaxSize/4
if ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)>0:
xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)
else:
xpos=0
if ctrl_pos[0]+picturecutof>self.NewW:
xpos=self.NewW-picturecutof
if ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)>0:
ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewW)/2)
else:
ypos=0
if ctrl_pos[1]+picturecutof>self.NewH:
ypos=self.NewH-picturecutof
xpos=xpos*self.W/self.NewW
ypos=ypos*self.H/self.NewH
picturecutofx=picturecutof*self.W/self.NewW
picturecutofy=picturecutof*self.H/self.NewH
rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
self.img=self.img.GetSubImage(rectangle)
self.img=self.img.Scale(600,600,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
self.zoomed=True
def onView(self,event=None):
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
self.W = self.img.GetWidth()
self.H = self.img.GetHeight()
if self.W > self.H:
self.NewW = self.PhotoMaxSize
self.NewH = self.PhotoMaxSize * self.H / self.W
else:
self.NewH = self.PhotoMaxSize
self.NewW = self.PhotoMaxSize * self.W / self.H
self.img = self.img.Scale(self.NewW,self.NewH,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
if __name__ == '__main__':
app = ImageTest()
app.MainLoop()
There maybe some wird stuff happening in the code that doesnt make entirely sense. Most of it is because the normal programm is much bigger and i removed many features in the prototpy that are having nothing to do with the zooming. It might very well be that i do the scaling wrong. But im out of ideas.
The Functionality for this protopye is simple: Replace the Path to a jpge image of your choiche. Run the programm. Click on the image and it should zoom. Click around your image and you will see zooming is wrong.
Thats it. Thanks for your help.
So i found the answer. But i changed something on the logic also. The picture will now be centered at the position where the user clicked. This is much more intuitive to use. I onle post the onImageClick Function. If you want to use the whole thing feel free to replace it in the original code from the question.
def onImageClick(self,event):
if self.zoomed:
self.onView()
self.zoomed=False
else :
#Determin position of mouse
ctrl_pos = event.GetPosition()
#Set magnification.
scalingfactor=4
#Set Picture size for rectangle
picturecutof=self.PhotoMaxSize/scalingfactor
##Find coordinates by adjusting for picture position
xpos=ctrl_pos[0]-((self.PhotoMaxSize-self.NewW)/2)
ypos=ctrl_pos[1]-((self.PhotoMaxSize-self.NewH)/2)
#if position is out of range adjust
if xpos>self.NewW:
xpos=self.NewW
if xpos<0:
xpos=0
if ypos>self.NewH:
ypos=self.NewH
if ypos<0:
ypos=0
#scale rectangle area to size of the unscaled image
picturecutofx=picturecutof*self.W/self.NewW
picturecutofy=picturecutof*self.H/self.NewH
#scale coordinates to unscaled image
xpos=xpos*self.W/self.NewW
ypos=ypos*self.H/self.NewH
#centeres image onto the coordinates where they were clicked
xpos=xpos-((ctrl_pos[0]*self.W/self.NewW)/scalingfactor)
ypos=ypos-((ctrl_pos[1]*self.H/self.NewH)/scalingfactor)
#if position is out of range adjust
if xpos>self.W-picturecutofx:
xpos=self.W-picturecutofx-5
if xpos<0:
xpos=0
if ypos>self.H-picturecutofy:
ypos=self.H-picturecutofy-5
if ypos<0:
ypos=0
#create rectangle to cut from original image
rectangle=wx.Rect(xpos,ypos,picturecutofx,picturecutofy)
#load original image again
self.img = wx.Image(self.imagepath, wx.BITMAP_TYPE_ANY)
#get subimage
self.img=self.img.GetSubImage(rectangle)
#scale subimage to picture area
self.img=self.img.Scale(self.PhotoMaxSize,self.PhotoMaxSize,wx.IMAGE_QUALITY_BICUBIC)
self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
self.imageCtrl.Fit()
self.panelleft.Refresh()
self.zoomed=True
event.Skip()

Godot 3.1 telling me that lock() and get_pixel() are nonexistent functions

I'm attempting to generate a height map from a noise texture. As far as I understand, in order to call get_pixel() on an image in this context, the image must first be locked. However, when I attempt to run the program, it exits with the error: Invalid call. Nonexistent function 'lock' in base 'StreamTexture'.
If I attempt to run it without locking the image, I get the error: Invalid call. Nonexistent function 'get_pixel' in base 'StreamTexture'.
I am certain that the instructions that I am following are for the same version of Godot I am running (3.1), so why is the engine telling me that lock() and get_pixel() are nonexistent functions?
My code is here:
extends Spatial
var width
var height
var heightData = {}
var vertices = PoolVector3Array()
var drawMesh = Mesh.new()
func _ready():
var noiseTexture = load("res://noiseTexture.png")
width = noiseTexture.get_width()
height = noiseTexture.get_height()
noiseTexture.lock()
for x in range(0, width):
for y in range(0, height):
heightData[Vector2(x,y)] = noiseTexture.get_pixel(x,y).r
noiseTexture.unlock()
for x in range(0, width-1):
for y in range(0, height-1):
createQuad(x,y)
var surfTool = SurfaceTool.new()
surfTool.begin(Mesh.PRIMITIVE_TRIANGLES)
for i in vertices.size():
surfTool.add_vertex(vertices[i])
surfTool.commit(drawMesh)
$MeshInstance.mesh = drawMesh
func createQuad(x,y):
#First half
vertices.push_back(Vector3(x, heightData[Vector2(x,y)], -y))
vertices.push_back(Vector3(x, heightData[Vector2(x,y+1)], -y-1))
vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y+1)], -y-1))
#Second Half
vertices.push_back(Vector3(x, heightData[Vector2(x,y)], -y))
vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y+1)], -y-1))
vertices.push_back(Vector3(x+1, heightData[Vector2(x+1,y)], -y))
Any help is greatly appreciated.
EDIT - I have (tried) to implement the changes that were suggested in the comments (yet I still don't know what to do with the color variable) and have attached a screenshot of my resulting code, as well as some comments I have made to try and explain to myself why the process SHOULD be working (I think). It also shows my node structure, which is why I opted to display this as an image. However, when I try to run this, the program crashes with the error displayed.
Check the docs;
StreamTexture does not have the method lock.
I think the class you are looking to use is Image. The Texture class is typically intended for drawing on the screen or applying to a Material
var noiseImage = Image.new()
noiseImage.load("res://noiseTexture.png")
noiseImage.lock() # Lock the image here
var color = noiseImage.get_pixel(10, 10) # Replace with your height map population
PS:
Just to let you know, I had a lot of issues with memory usage here so make sure you test that also (C# has bad garbage collector though).
You might need to dispose of the image, surface tool, and array mesh (If you remove the terrain object) to maintain optimum performance.
I have run into similar issues with generating heightmap terrains.
nathanfranke is correct and his solution will work.
If you for some reason use an ImageTexture you can call get_data() on that to get the underlying Image. Then you call lock() on the Image just like nathan says in his answer.
Take care to check that your coordinates for get_pixel() are correct. You can set a breakpoint by clicking on the very left edge of the line of code.
I mention this because I was very frustrated until I realized that my coordinate calculations were all int which resulted in the sampled pixel always being at <0,0>.
Here is part of my code for sampling an image into the HeightMapShape.map_data for Bullet heightmap collisions:
var map_w = shape.map_width
var map_h = shape.map_depth
var img_w = img.get_width()
var img_h = img.get_height()
img.lock()
for y in range(0, map_h):
py = float(img_h) * (float(y) / float(map_h))
for x in range(0, map_w):
px = float(img_w) * (float(x) / float(map_w))
index = y * img_h + x
pix = img.get_pixel(px, py)
shp.map_data[index] = pix.r * heightmap_height + heightmap_offset

Python load timer [duplicate]

So I have a .gif picture on a canvas in tkinter. I want this picture to change to another picture...but only for 3 seconds. and for it revert back to the original picture.
def startTurn(self):
newgif = PhotoImage(file = '2h.gif')
self.__leftImageCanvas.itemconfigure(self.__leftImage, image = newgif)
self.__leftImageCanvas.image = newgif
while self.cardTimer > 0:
time.sleep(1)
self.cardTimer -=1
oldgif = PhotoImage(file = 'b.gif')
self.__leftImageCanvas.itemconfigure(self.__leftImage, image = oldgif)
self.__leftImageCanvas.image = oldgif
this is a first attempt after a quick view of the timer. i know that this code does not make sense, but before i keep mindlessly trying to figure it out, i would much rather have more experienced input.
Tkinter widgets have a method named after which can be used to run a function after a specified period of time. To create an image and then change it three seconds later, you would do something like this:
def setImage(self, filename):
image = PhotoImage(file=filename)
self.__leftImageCanvas.itemconfigure(self.__leftImage, image=image)
self.__leftImageCanvas.image = image
def startTurn(self):
'''Set the image to "2h.gif", then change it to "b.gif" 3 seconds later'''
setImage("2h.gif")
self.after(3000, lambda: self.setImage("b.gif"))

Resources