memories/src/services/Layout.ts

178 lines
5.1 KiB
TypeScript
Raw Normal View History

import justifiedLayout from "justified-layout";
/**
* Generate the layout matrix.
*
* If we are in square mode, do this manually to get non-uniformity.
* Otherwise, use flickr/justified-layout (at least for now).
*/
export function getLayout(
input: { width: number, height: number }[],
opts: {
rowWidth: number,
rowHeight: number,
squareMode: boolean,
numCols: number,
}
): {
top: number,
left: number,
width: number,
height: number,
}[] {
if (!opts.squareMode) {
return justifiedLayout((input), {
containerPadding: 0,
boxSpacing: 0,
containerWidth: opts.rowWidth,
targetRowHeight: opts.rowHeight,
targetRowHeightTolerance: 0.1,
}).boxes;
}
// Binary flags
2022-10-17 22:46:38 +00:00
const FLAG_USE = 1 << 0;
const FLAG_USED = 1 << 1;
const FLAG_USE4 = 1 << 2;
const FLAG_USE6 = 1 << 3;
// Create 2d matrix to work in
const origRowLen = Math.ceil(input.length / opts.numCols);
const matrix: number[][] = new Array(origRowLen * 3); // todo: dynamic length
for (let i = 0; i < matrix.length; i++) {
matrix[i] = new Array(opts.numCols).fill(0);
}
// Fill in the matrix
let row = 0;
let col = 0;
let photoId = 0;
while (photoId < input.length) {
// Check if we reached the end of row
if (col >= opts.numCols) {
row++; col = 0;
}
// Check if already used
if (matrix[row][col] & FLAG_USED) {
col++; continue;
}
// Use this slot
matrix[row][col] |= FLAG_USE;
// Check if previous row has something used
// or something beside this is used
// We don't do these one after another
if ((row > 0 && matrix[row-1].some(v => v & FLAG_USED)) ||
2022-10-17 22:46:38 +00:00
(col > 0 && matrix[row][col-1] & FLAG_USED)
) {
2022-10-17 22:46:38 +00:00
photoId++; col++; continue;
}
2022-10-17 22:46:38 +00:00
// Number of photos left
const numLeft = input.length-photoId-1;
// Number of photos needed for perfect fill after using n
const needFill = (n: number) => ((opts.numCols-col-2) + (n/2-1)*(opts.numCols-2));
// Check if we can use 4 blocks
let canUse4 =
// We have enough space
(row + 1 < matrix.length && col+1 < opts.numCols) &&
// This cannot end up being a widow (conservative)
2022-10-17 22:46:38 +00:00
// Also make sure the next row gets fully filled, otherwise looks weird
(numLeft === needFill(4) || numLeft >= needFill(4)+opts.numCols);
let canUse6 =
// Image is portrait
input[photoId].height > input[photoId].width &&
// We have enough space
(row + 2 < matrix.length && col+1 < opts.numCols) &&
// This cannot end up being a widow (conservative)
// Also make sure the next row gets fully filled, otherwise looks weird
(numLeft === needFill(6) || numLeft >= needFill(6)+2*opts.numCols);
if (canUse6 && Math.random() < 0.2) {
// Use 6
matrix[row][col] |= FLAG_USE6;
matrix[row+1][col] |= FLAG_USED;
matrix[row+2][col] |= FLAG_USED;
matrix[row][col+1] |= FLAG_USED;
matrix[row+1][col+1] |= FLAG_USED;
matrix[row+2][col+1] |= FLAG_USED;
}
// Use four with 60% probability
2022-10-17 22:46:38 +00:00
else if (canUse4 && Math.random() < 0.5) {
// Use 4
matrix[row][col] |= FLAG_USE4;
matrix[row+1][col] |= FLAG_USED;
matrix[row][col+1] |= FLAG_USED;
matrix[row+1][col+1] |= FLAG_USED;
}
// Go ahead
2022-10-17 22:46:38 +00:00
photoId++; col++;
}
// Square layout matrix
const absMatrix: {
top: number,
left: number,
width: number,
height: number,
}[] = [];
let currTop = 0;
row = 0; col = 0; photoId = 0;
while (photoId < input.length) {
// Check if we reached the end of row
if (col >= opts.numCols) {
row++; col = 0;
currTop += opts.rowHeight;
continue;
}
// Skip if used
if (!(matrix[row][col] & FLAG_USE)) {
col++; continue;
}
// Create basic object
const sqsize = opts.rowHeight;
const p = {
top: currTop,
left: col * sqsize,
width: sqsize,
height: sqsize,
}
// Use twice the space
2022-10-17 22:46:38 +00:00
const v = matrix[row][col];
if (v & FLAG_USE4) {
p.width *= 2;
p.height *= 2;
col += 2;
2022-10-17 22:46:38 +00:00
} else if (v & FLAG_USE6) {
p.width *= 2;
p.height *= 3;
col += 2;
} else {
col += 1;
}
absMatrix.push(p);
photoId++;
}
return absMatrix;
2022-10-17 19:20:42 +00:00
}
function flagMatrixStr(matrix: number[][], numFlag: number) {
let str = '';
for (let i = 0; i < matrix.length; i++) {
const rstr = matrix[i].map(v => v.toString(2).padStart(numFlag, '0')).join(' ');
str += i.toString().padStart(2) + ' | ' + rstr + '\n';
}
return str;
}