refactor: use consistent preview sizes (fix #679)

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/685/head
Varun Patil 2023-06-04 10:44:35 -07:00
parent 078f4f7a5c
commit 4e283ecc93
12 changed files with 95 additions and 31 deletions

View File

@ -107,7 +107,7 @@ trait TimelineQueryMap
// SELECT these files from the filecache // SELECT these files from the filecache
$query = $this->connection->getQueryBuilder(); $query = $this->connection->getQueryBuilder();
$query->select('m.fileid', 'm.dayid', 'm.mapcluster', 'f.etag') $query->select('m.fileid', 'm.dayid', 'm.mapcluster', 'm.h', 'm.w', 'f.etag')
->from('memories', 'm') ->from('memories', 'm')
->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid')) ->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid'))
->where($query->expr()->in('m.fileid', $query->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY))) ->where($query->expr()->in('m.fileid', $query->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY)))
@ -119,6 +119,8 @@ trait TimelineQueryMap
$row['fileid'] = (int) $row['fileid']; $row['fileid'] = (int) $row['fileid'];
$row['mapcluster'] = (int) $row['mapcluster']; $row['mapcluster'] = (int) $row['mapcluster'];
$row['dayid'] = (int) $row['dayid']; $row['dayid'] = (int) $row['dayid'];
$row['h'] = (int) $row['h'];
$row['w'] = (int) $row['w'];
} }
return $files; return $files;

View File

@ -48,7 +48,7 @@ declare global {
var mViewer: { var mViewer: {
open: (anchorPhoto: IPhoto, rows: IRow[]) => Promise<void>; open: (anchorPhoto: IPhoto, rows: IRow[]) => Promise<void>;
openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: number): Promise<void>; openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: 256 | 512): Promise<void>;
close: () => void; close: () => void;
isOpen: () => boolean; isOpen: () => boolean;
}; };

View File

@ -68,7 +68,7 @@ export default defineComponent({
etag: this.album.album_id, etag: this.album.album_id,
flag: 0, flag: 0,
} as unknown as IPhoto; } as unknown as IPhoto;
return getPreviewUrl(mock, true, 512); return getPreviewUrl({ photo: mock, sqsize: 512 });
} }
return API.CLUSTER_PREVIEW(this.data.cluster_type, this.data.cluster_id); return API.CLUSTER_PREVIEW(this.data.cluster_type, this.data.cluster_id);

View File

@ -17,7 +17,7 @@
<div class="previews fill-block"> <div class="previews fill-block">
<div class="preview-container fill-block"> <div class="preview-container fill-block">
<div class="img-outer" v-for="info of previews" :key="info.fileid"> <div class="img-outer" v-for="info of previews" :key="info.fileid">
<XImg class="ximg fill-block" :src="getPreviewUrl(info, true, 256)" /> <XImg class="ximg fill-block" :src="previewUrl(info)" />
</div> </div>
</div> </div>
</div> </div>
@ -28,7 +28,7 @@
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import { IFolder, IPhoto } from '../../types'; import { IFolder, IPhoto } from '../../types';
import { getPreviewUrl } from '../../services/utils/helpers'; import * as utils from '../../services/utils/helpers';
import UserConfig from '../../mixins/UserConfig'; import UserConfig from '../../mixins/UserConfig';
import FolderIcon from 'vue-material-design-icons/Folder.vue'; import FolderIcon from 'vue-material-design-icons/Folder.vue';
@ -53,8 +53,6 @@ export default defineComponent({
previews: [] as IPhoto[], previews: [] as IPhoto[],
// Error occured fetching thumbs // Error occured fetching thumbs
error: false, error: false,
// Passthrough
getPreviewUrl,
}), }),
computed: { computed: {
@ -107,6 +105,14 @@ export default defineComponent({
} }
} }
}, },
/** Get preview url */
previewUrl(info: IPhoto) {
return utils.getPreviewUrl({
photo: info,
sqsize: 256,
});
},
}, },
}); });
</script> </script>

View File

@ -160,7 +160,7 @@ export default defineComponent({
/** Get url of the photo */ /** Get url of the photo */
url() { url() {
let base = 256; let base: 256 | 512 = 256;
// Check if displayed size is larger than the image // Check if displayed size is larger than the image
if (this.data.dispH! > base * 0.9 && this.data.dispW! > base * 0.9) { if (this.data.dispH! > base * 0.9 && this.data.dispW! > base * 0.9) {
@ -172,13 +172,10 @@ export default defineComponent({
base = 512; base = 512;
} }
// Make the shorter dimension equal to base return utils.getPreviewUrl({
let size = base; photo: this.data,
if (this.data.w && this.data.h) { msize: base,
size = Math.floor((base * Math.max(this.data.w, this.data.h)) / Math.min(this.data.w, this.data.h)) - 1; });
}
return utils.getPreviewUrl(this.data, false, size);
}, },
/** Set src with overlay face rect */ /** Set src with overlay face rect */

View File

@ -91,13 +91,12 @@ export default defineComponent({
methods: { methods: {
toCoverUrl(fileId: string | number) { toCoverUrl(fileId: string | number) {
return getPreviewUrl( return getPreviewUrl({
{ photo: {
fileid: Number(fileId), fileid: Number(fileId),
} as IPhoto, } as IPhoto,
true, sqsize: 256,
256 });
);
}, },
albumCreatedHandler() { albumCreatedHandler() {

View File

@ -154,7 +154,10 @@ export default defineComponent({
}, },
async sharePreview() { async sharePreview() {
const src = utils.getPreviewUrl(this.photo!, false, 2048); const src = utils.getPreviewUrl({
photo: this.photo!,
size: 2048,
});
this.shareWithHref(src, true); this.shareWithHref(src, true);
}, },

View File

@ -281,7 +281,10 @@ export default defineComponent({
}, },
clusterPreviewUrl(cluster: IMarkerCluster) { clusterPreviewUrl(cluster: IMarkerCluster) {
return utils.getPreviewUrl(cluster.preview, false, 256); return utils.getPreviewUrl({
photo: cluster.preview,
msize: 256,
});
}, },
clusterIconClass(cluster: IMarkerCluster) { clusterIconClass(cluster: IMarkerCluster) {

View File

@ -151,7 +151,10 @@ export default defineComponent({
// Get random photo // Get random photo
year.preview ||= utils.randomChoice(year.photos); year.preview ||= utils.randomChoice(year.photos);
year.url = utils.getPreviewUrl(year.preview, false, 512); year.url = utils.getPreviewUrl({
photo: year.preview,
msize: 512,
});
} }
await this.$nextTick(); await this.$nextTick();

View File

@ -49,7 +49,7 @@ export default defineComponent({
return { return {
source: source:
this.photo.h && this.photo.w this.photo.h && this.photo.w
? utils.getPreviewUrl(this.photo, false, 'screen') ? utils.getPreviewUrl({ photo: this.photo, size: 'screen' })
: API.IMAGE_DECODABLE(this.photo.fileid, this.photo.etag), : API.IMAGE_DECODABLE(this.photo.fileid, this.photo.etag),
defaultSavedImageName: this.defaultSavedImageName, defaultSavedImageName: this.defaultSavedImageName,

View File

@ -694,7 +694,8 @@ export default defineComponent({
preload(dayIdx + 3); preload(dayIdx + 3);
// Get thumb image // Get thumb image
const thumbSrc: string = this.thumbElem(photo)?.getAttribute('src') || utils.getPreviewUrl(photo, false, 256); const thumbSrc: string =
this.thumbElem(photo)?.getAttribute('src') || utils.getPreviewUrl({ photo, msize: 256 });
// Get full image // Get full image
return { return {
@ -739,7 +740,7 @@ export default defineComponent({
}, },
/** Open with a static list of photos */ /** Open with a static list of photos */
async openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: number) { async openStatic(photo: IPhoto, list: IPhoto[], thumbSize?: 256 | 512) {
this.list = list; this.list = list;
const photoswipe = await this.createBase({ const photoswipe = await this.createBase({
index: list.findIndex((p) => p.fileid === photo.fileid), index: list.findIndex((p) => p.fileid === photo.fileid),
@ -750,7 +751,7 @@ export default defineComponent({
photoswipe.addFilter('itemData', (itemData, index) => ({ photoswipe.addFilter('itemData', (itemData, index) => ({
...this.getItemData(this.list[index]), ...this.getItemData(this.list[index]),
msrc: thumbSize ? utils.getPreviewUrl(photo, false, thumbSize) : undefined, msrc: thumbSize ? utils.getPreviewUrl({ photo, msize: thumbSize }) : undefined,
})); }));
this.isOpen = true; this.isOpen = true;
@ -759,7 +760,7 @@ export default defineComponent({
/** Get base data object */ /** Get base data object */
getItemData(photo: IPhoto) { getItemData(photo: IPhoto) {
let previewUrl = utils.getPreviewUrl(photo, false, 'screen'); let previewUrl = utils.getPreviewUrl({ photo, size: 'screen' });
const isvideo = photo.flag & this.c.FLAG_IS_VIDEO; const isvideo = photo.flag & this.c.FLAG_IS_VIDEO;
// Preview aren't animated // Preview aren't animated

View File

@ -11,8 +11,48 @@ export function isMobile() {
return globalThis.windowInnerWidth <= 768; return globalThis.windowInnerWidth <= 768;
} }
/** Get preview URL from photo object */ /** Preview generation options */
export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') { type PreviewOpts = {
/** Photo object to create preview for */
photo: IPhoto;
};
type PreviewOptsSize = PreviewOpts & {
/**
* Directly specify the size of the preview.
* If you already know the size of the photo, use msize instead,
* so that caching can be utilized best. A size of 256 is not allowed
* here size the thumbnails are not pre-generated.
*/
size: 512 | 1024 | 2048 | [number, number] | 'screen';
};
type PreviewOptsMsize = PreviewOpts & {
/**
* Size of minimum edge of the preview (recommended).
* This can only be used if the photo object has width and height.
*/
msize: 256 | 512 | 1024 | 2048;
};
type PreviewOptsSquare = PreviewOpts & {
/**
* Size of the square preview.
* Note that these will still be cached and requested with XImg multipreview.
*/
sqsize: 256 | 512 | 1024;
};
/**
* Get preview URL from photo object
*
* @param opts Preview options
*/
export function getPreviewUrl(opts: PreviewOptsSize | PreviewOptsMsize | PreviewOptsSquare) {
// Destructure does not work with union types
let { photo, size, msize, sqsize } = opts as PreviewOptsSize & PreviewOptsMsize & PreviewOptsSquare;
// Square size is just size
const square = sqsize !== undefined;
if (square) size = sqsize as any;
// Native preview // Native preview
if (photo.flag & constants.c.FLAG_IS_LOCAL) { if (photo.flag & constants.c.FLAG_IS_LOCAL) {
return API.Q(nativex.API.IMAGE_PREVIEW(photo.fileid), { c: photo.etag }); return API.Q(nativex.API.IMAGE_PREVIEW(photo.fileid), { c: photo.etag });
@ -25,8 +65,18 @@ export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [nu
size = [sw, sh]; size = [sw, sh];
} }
// Base size conversion
if (msize !== undefined) {
if (photo.w && photo.h) {
size = (Math.floor((msize * Math.max(photo.w, photo.h)) / Math.min(photo.w, photo.h)) - 1) as any;
} else {
console.warn('Photo has no width or height but using msize');
size = msize === 256 ? 512 : msize;
}
}
// Convert to array // Convert to array
const [x, y] = typeof size === 'number' ? [size, size] : size; const [x, y] = typeof size === 'number' ? [size, size] : size!;
return API.Q(API.IMAGE_PREVIEW(photo.fileid), { return API.Q(API.IMAGE_PREVIEW(photo.fileid), {
c: photo.etag, c: photo.etag,