How can I scale an image to keep it's aspect ratio inside a viewBox in the app?

I see SVG supports viewBox commands to constrain images inside the box (helpful for temporary images that are not all formatted to the same size.)

How can I use that command in your app?

6 replies

FYI, versions 1.2.1 and later include +imageFit and +imageStretch as alternatives to +imageFill.

Yes, thanks!

Perhaps in the future, you can add a attribute to +imageFill function to support each of the states in your Google graphic.

Also, how did you insert your graphic into your response - it appears Insert Image only has URL support?

For inserting the graphic, I found the image on the internet and put it in by URL.  I'm also a fan of https://snag.gy for uploading screenshots.

I learned something new today!  SVG actually has Aspect Fill as a built-in feature, in addition to Aspect Fit.  My little +imageFill function is actually not needed.  You can do each one as follows:

// Stretch:
image(x=40, y=51, width=100, height=100,

// Aspect Fit:
image(x=40, y=51, width=100, height=100,
      preserveAspectRatio="xMidYMid meet")

// Aspect Fill:
image(x=40, y=51, width=100, height=100,
      preserveAspectRatio="xMidYMid slice")

The +imageFill function scales images to fill the entire box and crops the image on the edges.  This is called "Aspect Fill" in this diagram:


It sounds like you want to instead fit the image inside the box even if it doesn't fill the entire box, or "Aspect Fit" in the diagram.  This is actually SVG's default behavior with image.  If you use the SVG image tag directly, you should get Aspect Fit.

image(x=40, y=51, width=100, height=100, xlink:href=image.dataUri)

Does that work?


Thanks for the reply, Shane. 

Somehow, I'm not seeing this, taller or wider images are being cropped (I can't see how to attach a local graphic here - email me directly if you want to see screenshot examples)

Here's my code (no code before this in Template):

rect(x=0 y=0 width=180 height=252 fill="black")
+imageFill(image, 40, 51, 100, 100)
// [put card image under the card background]
+imageFill(assets.background, -10, -10, 200, 272)

Any ideas why I'm not seeing the image's AR constrained to the box?

The most dead-simple way to embed an image into your template is to use the SVG "image" tag and link your image as follows:

image(x=0 y=0 width=100 height=100 xlink:href=image_field.dataUri)

where "image_field" is the name of a field in the spreadsheet containing an image (the field must have the "img" property selected).

However, Card Creatr comes with a built-in function called "imageFill" that takes an image and scales it to fill the given rectangle, cropping the image on the edges to preserve aspect ratio.  It is used in most of the templates on the web site.

+imageFill(image_field, 40, 70, 100, 85)  // x, y, width, height

Under the hood, the function expands to the following SVG (shown here with XML syntax, not pug syntax, for clarity):

  <clipPath id="imageFill-1">
    <rect x="40" y="70" width="100" height="85"/>

In words, the image is placed with calculated coordinates and size, and then a "clip path" (vector mask) cuts out the part of the image to actually display.

For reference, the implementation of the +imageFill function is as follows (warning, this is a bit technical):

- _imageFillId = 0
mixin imageFill(image, x, y, width, height)
                        rect(x=x, y=y, width=width, height=height)
        - landscape = (width/height) > (image.dims.width/image.dims.height)
        - imgWidth = landscape ? width : height*image.dims.width/image.dims.height
        - imgHeight = landscape ? width*image.dims.height/image.dims.width : height
        - imgX = landscape ? x : x - (imgWidth-width)/2
        - imgY = landscape ? y - (imgHeight-height)/2 : y
        image(x=imgX, y=imgY, width=imgWidth, height=imgHeight, clip-path="url(#imageFill-"+_imageFillId+")", xlink:href=image.dataUri)&attributes(attributes)
        - _imageFillId += 1

Let me know if this helps!