Where this account begins: every cell is a square
Send one batch of photos into square cells and the landscape shots get 25% cut away, the portrait shots get 43.75% cut away, and a square composition keeps every pixel it has. The difference is not in the template. All 7 templates (the templates array, line 86-94) build square cells, and slotRatio stays at 1 the whole time: in the shape family the pw/ph pair read at line 305 is equal, and in the grid family the pair at line 340 is 300/300. The percentage that gets cropped is therefore tied only to the aspect ratio of the source image, and templates differ from each other merely in target side length, fixed at 300 for the grid family and at 592 for the default shape family.
How much landscape, portrait and square each lose
The crop branch sits at line 343-347: a landscape image is trimmed left and right along its width, a portrait image is trimmed top and bottom along its height, and a square image needs no offset at all.
| Source image | Kept edge | Offset | Kept | Dropped | Pixels dropped |
|---|---|---|---|---|---|
| 4032×3024 | 3024×3024 | sx 504 | 75% | 25% | 3,048,192 |
| 1080×1920 | 1080×1080 | sy 420 | 56.25% | 43.75% | 907,200 |
| 640×640 | 640×640 | sx/sy 0 | All kept | 0% | 0 |
Mixed orientations have to be counted one cell at a time
Three landscape shots, three portrait shots and three square shots go into grid-3x3 with gap 8, and the source rectangle behind every drawImage call is captured cell by cell. The batch totals 44,027,904 source pixels, spends 32,161,728 of them and throws away 11,866,176, which is a batch dropPct of 26.9515. Three different answers live under that single number: landscape loses 25%, portrait loses 43.75% and square loses 0%. grid-2x2 at gap 0 accepts one landscape, one portrait, one square and one animated file, so the canvas is only 600×600 while 4 different crop decisions coexist inside the same picture. Changing the template on the collage page only changes the slots and the side length; it never changes how much of your photo is cut.
Canvas size comes straight out of the formula
The grid family runs through line 245-247, totalW=cols*300+(cols+1)*gap and cellW=cellH=300: at gap 0 / 8 / 40 grid-2x2 measures 600×600 / 624×624 / 720×720, while grid-3x3 measures 900×900 / 932×932 / 1060×1060. In the strip family the cell count equals the image count, so strip-h holding 300 files ends up as 92408×316. The shape family runs through line 286-287, where cellSize=floor(canvasW/density) is reduced by the gap again, so a canvas width of 1200 at density 2 gives 600→592, and the number of cells is always density*(density+1), which reads out as 6 / 12 / 30 / 110 / 420.
Gaps shave the background, shapes shave most of the canvas
Count the pixels that match the background colour first. grid-2x2 at gap 8 is a 624×624 canvas, 389,376 px in total, and only 29,376 px of it stay bare background, which is 7.5444%, so the photo ink covers 92.4556%. At gap 40 the same template covers 518,400 px and 158,400 px of that is background. Rounded corners barely register in the same ledger: the 3×3 set drawn with radius 30 stops painting only 6,696 px, and the canvas usage rate is still 99.1733%.
The shape family is where the pixels truly disappear. Under the default parameters, shape size 60 and density 2, a finished 1,440,000 px image is only 22.543% ink for the circle, 48.9476% for the heart and 12.6408% for the diamond. Everything else on that canvas is the background colour the user selected, not photography.
There is no quality control on the upscaling side
Not one assignment to imageSmoothingEnabled or imageSmoothingQuality exists in the template source (grep finds 0 hits), and each drawImage reads back enabled=true with quality=low. A 1×1 transparent dot gets stretched into 300×300 inside a grid, a shareOfSrc of 90000, and into 592×592 inside the default shape. Read the same machinery in the other direction: 4032×3024 dropped into a single 300×300 cell keeps only 0.74% of its pixels, a shareOfSrc of 0.0074. Enlarging something 300 times and shrinking something down to 300 both travel the same browser default path, with no downgrade switch and no image quality parameter to turn.
Cell counts are hard-coded, image counts are not
Batches of 2 / 9 / 16 / 30 / 60 files are all accepted, and a single input carrying 300 files is accepted just the same, because there is no files.length branch anywhere in the code. UU.toast appears at only 3 places, line 140 / 376 / 384, and the wording is 请上传图片文件, 请先上传图片 and 拼图已下载. The cell counts, by contrast, are written down in stone: grid-2x2 always has 4 cells, grid-3x3 always has 9, and the shape family at default density 2 always has 6. Send 5 different images into grid-2x2 and only the first 4 are ever drawn; the fifth one, a 300×400 file, never appears in the drawImage record, yet the information line still reads 624 × 624 px | 5 images. When there are fewer images than cells, line 328 reuses them through (r*cols+c) % colImages.length. Neither kind of mismatch leaves an empty slot and neither one leaves a warning.
Export has a single PNG channel
The second argument of toBlob is image/png on every call (line 385), the third argument is never passed, and reqQuality measures undefined; the type reported by the callback blob is image/png as well. Nine createImageBitmap readbacks return decodedW/H pairs that match the canvas.width/height of that moment one by one, and the 680 you see on screen is only CSS, since dispScale at line 355-358 is capped at 1, so there is no second resize hiding after the canvas. The finished file equals the canvas in size, one grid cell is always 300×300, and 4 images of 4032×3024 assemble into a result of only 389,376 px. That is why the marketing sentence about a high definition PNG collage export can only be treated as a claim under audit.
Every failure line stays silent
corrupt.png at 72 B and corrupt_webp.webp at 16 B both arrive with a legal MIME type, and the added counter at line 138 does count them, but the template declares no img.onerror, so one image fails to load and 0 notices are raised. The real wall belongs to the browser: 65535 px on a single axis. strip-h with 218 files measures 65400×300 and still exports 7,849,564 B. With 219 files it becomes 65700×300, the bitmap is never allocated at all, the toBlob callback receives null, createObjectURL at line 379 throws a TypeError, and the notice at line 384 never runs. From the user side, the download button was clicked and nothing happened.
Three questions that came up
Q: why does a portrait image lose more than a landscape one?
A square cell can only be as long as the short edge. A portrait image has 1080 as its short edge, so its 1920 height must give up sy 420 at the top and the same at the bottom, a dropPct of 43.75 and 907,200 px thrown away. A landscape image has 3024 as its short edge and only needs sx 504 off the left and the right, a dropPct of 25 and 3,048,192 px thrown away.
Q: can a different template cut away less?
It cannot. slotRatio is 1 for all 7 templates, so switching templates only trades a side length of 300 for one of 592: landscape still loses 25%, portrait still loses 43.75% and square still loses 0%. Which photo goes into which slot and which shape to pick belong to the article on making nine-cell grid and heart shaped collages online; the two write-ups do not overlap.
Q: does it warn when too many images are loaded at once?
It does not. There is no branch on the number of images, everything past the available cells is skipped quietly, and only an axis longer than 65535 px actually fails. Even that one does not bother to say anything.