hrguez's avatar
hrguez
Copper Contributor
Oct 31, 2025
Status:
New

Replace image placeholder keeping aspect ratio and cropped areas of the source image

I have a taskpane add-in which lists a set of images, and has a button to insert images into the current slide. In other words, my add-in mimics the built-in stock images taskpane.

In the built-in stock images taskpane when an image is inserted into a slide with a placeholder for images, the image is inserted into the placeholder using a cover behaviour. Later one the user can adjust the visible part of the image via the crop options. The following image showcase the above flow.

I haven't manage to implement the COVER behaviour, only the CONTAIN one but it doesn't produce the expected result when the placeholder is not a square one.

Any ideas how to get the same result as the built-in option? Is this possible with the current PowerPoint JS API?

Here is my current implementation to insert images into placeholders. For simplicity it assumes the number of images matches the available placeholders.

export async function insertImagesDemo(base64Images: string[]) {
        await PowerPoint.run(async (context) => {
            const shapes: PowerPoint.ShapeCollection =
                context.presentation.slides.getItemAt(0).shapes;
            shapes.load('items');
            await context.sync();
            shapes.items.forEach((shape) => {
                shape.load('type');
            });

            const placeholders = shapes.items.filter(
                (shape) => shape.type === 'Placeholder',
            );

            placeholders.forEach((shape) => {
                shape.load('placeholderFormat');
                shape.placeholderFormat.load('type');
            });

            await context.sync();

            const imagePlaceholders = placeholders.filter(
                (shape) =>
                    shape.placeholderFormat.type === PowerPoint.PlaceholderType.picture,
            );

            // Insert images into placeholders
            for (let i = 0; i < imagePlaceholders.length; i++) {
                if (i < base64Images.length) {
                    const targetPlaceholder = imagePlaceholders[i];
                    const { left, top, height, width } = targetPlaceholder;
                    targetPlaceholder.delete();
                                                        
                    // Original image aspect ratio 1920, 1080
                    const imgW = 1920;
                    const imgH = 1080;
                    const imgAspect = imgW / imgH;
                    const boxAspect = width / height;

                    // CONTAIN MODE Compute scaled size that preserves aspect ratio
                    let finalWidth, finalHeight, offsetX = 0, offsetY = 0;
                    if (imgAspect > boxAspect) {
                        // image is wider — limit by width
                        finalWidth = width;
                        finalHeight = width / imgAspect;
                        offsetY = (height - finalHeight) / 2; // center vertically
                    } else {
                        // image is taller — limit by height
                        finalHeight = height;
                        finalWidth = height * imgAspect;
                        offsetX = (width - finalWidth) / 2; // center horizontally
                    }

                    //// Cover
                    // let finalWidth, finalHeight, offsetX = 0, offsetY = 0;
                    // // Compute scaled size that covers the placeholder
                    // if (imgAspect > boxAspect) {
                    // // Image is wider -> limit by height (crop sides)
                    // finalHeight = height;
                    // finalWidth =height * imgAspect;
                    // offsetX = (width - finalWidth) / 2;
                    // } else {
                    // // Image is taller -> limit by width (crop top/bottom)
                    // finalWidth = width;
                    // finalHeight = width / imgAspect;
                    // offsetY = (height - finalHeight) / 2;
                    // }

                    //Create a shape sized to the scaled dimensions
                    const shape = shapes.addGeometricShape(PowerPoint.GeometricShapeType.rectangle, {
                        left: left + offsetX,
                        top: top + offsetY,
                        width: finalWidth,
                        height: finalHeight,
                    });

                    shape.fill.setImage(base64Images[i]);
                    shape.lineFormat.visible = false;
                }
            }
            await context.sync();
        });
    }

 

No CommentsBe the first to comment