Open anki-xyz opened 9 years ago
I found a solution for the issue.
Adding a new parameter called method
to the insert_picture
function I changed a few lines of code in \shapes\placeholder.py:
def insert_picture(self, image_file, method = 'crop'):
"""
Return a |PlaceholderPicture| object depicting the image in
*image_file*, which may be either a path (string) or a file-like
object. The image is cropped to fill the entire space of the
placeholder. A |PlaceholderPicture| object has all the properties and
methods of a |Picture| shape except that the value of its
:attr:`~._BaseSlidePlaceholder.shape_type` property is
`MSO_SHAPE_TYPE.PLACEHOLDER` instead of `MSO_SHAPE_TYPE.PICTURE`.
"""
pic = self._new_placeholder_pic(image_file, method) # pass new parameter "method"
self._replace_placeholder_with(pic)
return PlaceholderPicture(pic, self._parent)
def _new_placeholder_pic(self, image_file, method = 'crop'):
"""
Return a new `p:pic` element depicting the image in *image_file*,
suitable for use as a placeholder. In particular this means not
having an `a:xfrm` element, allowing its extents to be inherited from
its layout placeholder.
"""
rId, desc, image_size = self._get_or_add_image(image_file)
id_, name = self.id, self.name
# Cropping the image, as in the original file
if method == 'crop':
pic = CT_Picture.new_ph_pic(id_, name, desc, rId)
pic.crop_to_fit(image_size, (self.width, self.height))
# Adjusting image to placeholder size and replace placeholder.
else:
aspectImg = image_size[0]/image_size[1]
aspectPh = self.width / self.height
if aspectPh > aspectImg:
h = self.height
w = int(aspectImg * self.height)
else:
w = self.width
h = int(aspectImg * w)
pic = CT_Picture.new_pic(id_, name, desc, rId, self.left, self.top, w, h)
return pic
Fairly, quick and dirty, but does the job. Improvements and implementation are welcome.
Hi @Anki11 ,
thx for digging into this and sharing of you hack. Exactly what I had need today. Cropping was not an option, if you need to add matplotlib charts to it :)
But after some fiddling and testing, this is my logic to calculate the final w/h.
Instead of multiplying it with the aspectImg
, I had to divide it for the else
branch, that the output makes sense for me:
ph_w, ph_h = self.width, self.height
aspectPh = ph_w / ph_h
img_w, img_h = image_size
aspectImg = img_w / img_h
if aspectPh > aspectImg:
w = int(ph_h * aspectImg)
h = ph_h # keep the height
else:
w = ph_w # keep the width
h = int(ph_w / aspectImg)
Just wondering is this solution in latest pptx code base? because i am still having this issue
No
any plans to add it next version?
+1 If possible I would also like to see this in the official version. It works fine for me
Alternatively here is insert_picture_with_fit_method branch, that will NOT change proportions of the image.
It will act almost the same way as original insert_picture
method, but instead of stretching image and cropping the oversize parts it will shrink it, keeping aspect ratio of an image and all original content visible. Image will be then aligned in the center of the placeholder (as it happens with original insert_picture
method).
This might be preferred when the proportions are critical.
Okay, so let me see if I understand the behavior you're looking for. Here's what I think you're asking for:
Insert an image into a picture placeholder, such that:
I'm thinking the basic idea here is to be able to specify the appropriate size for an image in the template deck, then fill it in with any variety of images without having to worry about formatting the image for proper visual size. Like you had a catalog of items and the picture for each item should appear the same size, but the source images are any variety of sizes. Is that about right?
Thank you for looking into this.
This is a perfect description of what I am looking for. Intel Deutschland GmbH Registered Address: Am Campeon 10-12, 85579 Neubiberg, Germany Tel: +49 89 99 8853-0, www.intel.de Managing Directors: Christin Eisenschmid, Christian Lamprechter Chairperson of the Supervisory Board: Nicole Lau Registered Office: Munich Commercial Register: Amtsgericht Muenchen HRB 186928
@scanny Thanks for looking into this and yes, this is basically what I have tried to implement in this patch. I have essentially only added one method _fit_resizing
(based on your original _fill_cropping
), which returns the same format of crop
tuple to .blipFill.crop
, but with negative values (to allow shrinking of the image).
Works fine so far with various matplotlib plots of varying sizes I have tried.
I thought boolean crop
parameter would suffice for this kind of operation:
placeholder.insert_picture(image, crop=False)
crop
defaults to True, so no impact on backward compatibility as far as I can see.
I would also love this functionality. It would make automatically pushing plots to powerpoint much easier.
Adding this functionality would be a huge plus and simplify dumping Matplotlib figures into PowerPoint.
Any update on if this will be implemented soon?
This is what I came up with. It replaces the placeholder with the image, setting the max size to the bounds of the placeholder and preserving aspect ratio. It also centers the image in the placeholder.
def replace_with_image(img, shape, slide):
pic = slide.shapes.add_picture(img, shape.left, shape.top)
#calculate max width/height for target size
ratio = min(shape.width / float(pic.width), shape.height / float(pic.height))
pic.height = int(pic.height * ratio)
pic.width = int(pic.width * ratio)
pic.left = shape.left + ((shape.width - pic.width) / 2)
pic.top = shape.top + ((shape.height - pic.height) / 2)
placeholder = shape.element
placeholder.getparent().remove(placeholder)
return
I implemented adding images to SlidePlaceholders by doing this:
class SlidePlaceholder(_BaseSlidePlaceholder):
"""
Placeholder shape on a slide. Inherits shape properties from its
corresponding slide layout placeholder.
"""
def insert_picture(self, image_file, crop=True):
"""
Return a |PlaceholderPicture| object depicting the image in
*image_file*, which may be either a path (string) or a file-like
object. The image is cropped to fill the entire space of the
placeholder. A |PlaceholderPicture| object has all the properties and
methods of a |Picture| shape except that the value of its
:attr:`~._BaseSlidePlaceholder.shape_type` property is
`MSO_SHAPE_TYPE.PLACEHOLDER` instead of `MSO_SHAPE_TYPE.PICTURE`.
"""
pic = self._new_placeholder_pic(image_file, crop) # pass new parameter "method"
self._replace_placeholder_with(pic)
return PlaceholderPicture(pic, self._parent)
def _new_placeholder_pic(self, image_file, crop=True):
"""
Return a new `p:pic` element depicting the image in *image_file*,
suitable for use as a placeholder. In particular this means not
having an `a:xfrm` element, allowing its extents to be inherited from
its layout placeholder.
"""
rId, desc, image_size = self._get_or_add_image(image_file)
id_, name = self.shape_id, self.name
# Cropping the image, as in the original file
if crop:
pic = CT_Picture.new_ph_pic(id_, name, desc, rId)
pic.crop_to_fit(image_size, (self.width, self.height))
# Adjusting image to placeholder size and replace placeholder.
else:
ph_w, ph_h = self.width, self.height
aspectPh = ph_w / ph_h
img_w, img_h = image_size
aspectImg = img_w / img_h
if aspectPh > aspectImg:
w = int(ph_h * aspectImg)
h = ph_h # keep the height
else:
w = ph_w # keep the width
h = int(ph_w / aspectImg)
top = self.top + (ph_h - h) / 2
left = self.left + (ph_w - w) / 2
pic = CT_Picture.new_pic(id_, name, desc, rId, self.left + (ph_w - w) / 2, self.top, w, h)
return pic
def _get_or_add_image(self, image_file):
"""
Return an (rId, description, image_size) 3-tuple identifying the
related image part containing *image_file* and describing the image.
"""
image_part, rId = self.part.get_or_add_image_part(image_file)
desc, image_size = image_part.desc, image_part._px_size
return rId, desc, image_size
@scanny Is the resize part of next release or is there a patch I can apply to resolve the resize issue?
I liked and used the code @sclem posted, but I ran into the issue that when the image placeholder had some animations on them, it would lose these properties due to the placeholder being removed.
I adapted his code and used the crop_bottom
and equivalent methods (which seems to be recommended in the documentation) to create the following method. I'm just sharing the code in case other people run into this problem as well. I suspect the code can be optimised further, but this works for my use cases:
from PIL import Image
def _add_image(slide, placeholder_id, image_url):
placeholder = slide.placeholders[placeholder_id]
# Calculate the image size of the image
im = Image.open(image_url)
width, height = im.size
# Make sure the placeholder doesn't zoom in
placeholder.height = height
placeholder.width = width
# Insert the picture
placeholder = placeholder.insert_picture(image_url)
# Calculate ratios and compare
image_ratio = width / height
placeholder_ratio = placeholder.width / placeholder.height
ratio_difference = placeholder_ratio - image_ratio
# Placeholder width too wide:
if ratio_difference > 0:
difference_on_each_side = ratio_difference / 2
placeholder.crop_left = -difference_on_each_side
placeholder.crop_right = -difference_on_each_side
# Placeholder height too high
else:
difference_on_each_side = -ratio_difference / 2
placeholder.crop_bottom = -difference_on_each_side
placeholder.crop_top = -difference_on_each_side
I have created a pull request that is based on the previous comments: https://github.com/scanny/python-pptx/pull/439
I came across the same issue. What I am looking for is the equivalent to "Fit" in the UI:
I saved ppts, one with fit, the other with fill and had a look at the source. I could only find a relevant diff in the xml of the slide:
I am wondering why the values for fit are negative and if there is a way to mimick this behavior without resizing the image or the placeholder.
That's probably what is mentioned here:
This could be elaborated to "center" the image within the original space and perhaps to use "negative cropping" to retain the original placeholder size.
Dear all,
I know that images are cropped to fit the placeholder container width and height. However, I'd like to use something like an automatic adjustment, meaning, if I load an image into the placeholder, the image should be resized/scaled to fit the placeholder having the aspect ratio locked. I was playing around with the placeholder.py file: Removing the cropping resizes the image to the full container width and height, however, when setting the width and height of the placeholder inside of that function, nothing happens. When setting the width after adding the image manually, the images disappear from the slide.
It would be great if there would a solution.
Thanks a lot for the help best anki