Skip to content

Commit

Permalink
feat(web): load fullsize preview for RAW images when zoomed in
Browse files Browse the repository at this point in the history
  • Loading branch information
eligao committed Dec 2, 2024
1 parent c06f581 commit f2b631a
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 23 deletions.
14 changes: 10 additions & 4 deletions web/src/lib/components/asset-viewer/photo-viewer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,23 @@ describe('PhotoViewer component', () => {
const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' });
render(PhotoViewer, { asset });

expect(getAssetThumbnailUrlSpy).not.toBeCalled();
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum });
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
id: asset.id,
size: AssetMediaSize.Original,
checksum: asset.checksum,
});
});

it('loads original for shared link when download permission is true and showMetadata permission is true', () => {
const asset = assetFactory.build({ originalPath: 'image.gif', originalMimeType: 'image/gif' });
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
render(PhotoViewer, { asset, sharedLink });

expect(getAssetThumbnailUrlSpy).not.toBeCalled();
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum });
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
id: asset.id,
size: AssetMediaSize.Original,
checksum: asset.checksum,
});
});

it('not loads original image when shared link download permission is false', () => {
Expand Down
46 changes: 27 additions & 19 deletions web/src/lib/components/asset-viewer/photo-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { isWebCompatibleImage, canCopyImageToClipboard, copyImageToClipboard } from '$lib/utils/asset-utils';
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import {
isRawImage,
isWebCompatibleImage,
canCopyImageToClipboard,
copyImageToClipboard,
} from '$lib/utils/asset-utils';
import { getBoundingBox } from '$lib/utils/people-utils';
import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize, AssetTypeEnum, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk';
Expand Down Expand Up @@ -66,25 +71,23 @@
$boundingBoxesArray = [];
});
const preload = (useOriginal: boolean, preloadAssets?: AssetResponseDto[]) => {
const getAssetUrl = (id: string, targetSize: AssetMediaSize, checksum: string) => {
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum });
}
return getAssetThumbnailUrl({ id, size: targetSize, checksum });
};
const preload = (targetSize: AssetMediaSize, preloadAssets?: AssetResponseDto[]) => {
for (const preloadAsset of preloadAssets || []) {
if (preloadAsset.type === AssetTypeEnum.Image) {
let img = new Image();
img.src = getAssetUrl(preloadAsset.id, useOriginal, preloadAsset.checksum);
img.src = getAssetUrl(preloadAsset.id, targetSize, preloadAsset.checksum);
}
}
};
const getAssetUrl = (id: string, useOriginal: boolean, checksum: string) => {
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum });
}
return useOriginal
? getAssetOriginalUrl({ id, checksum })
: getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum });
};
copyImage = async () => {
if (!canCopyImageToClipboard()) {
return;
Expand Down Expand Up @@ -144,21 +147,26 @@
loader?.removeEventListener('error', onerror);
};
});
let isWebCompatible = $derived(isWebCompatibleImage(asset));
// RAW files may have corresponding extracted JPEGs
let isRaw = $derived(isRawImage(asset));
let useOriginalByDefault = $derived(isWebCompatible && $alwaysLoadOriginalFile);
// when true, will force loading of the original image
// when true, will force loading of the original image
let forceUseOriginal: boolean = $derived(
asset.originalMimeType === 'image/gif' || ($photoZoomState.currentZoom > 1 && isWebCompatible),
asset.originalMimeType === 'image/gif' || ($photoZoomState.currentZoom > 1 && (isWebCompatible || isRaw)),
);
let useOriginalImage = $derived(useOriginalByDefault || forceUseOriginal);
const targetImageSize = $derived(
useOriginalByDefault || forceUseOriginal ? AssetMediaSize.Original : AssetMediaSize.Preview,
);
$effect(() => {
preload(useOriginalImage, preloadAssets);
preload(targetImageSize, preloadAssets);
});
let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.checksum));
let imageLoaderUrl = $derived(getAssetUrl(asset.id, targetImageSize, asset.checksum));
</script>

<svelte:window
Expand Down
38 changes: 38 additions & 0 deletions web/src/lib/utils/asset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,44 @@ export function isWebCompatibleImage(asset: AssetResponseDto): boolean {
return supportedImageMimeTypes.has(asset.originalMimeType);
}

// TODO: share with server
const supportedRawImageMimeTypes = new Set([
'image/3fr',
'image/ari',
'image/arw',
'image/cap',
'image/cin',
'image/cr2',
'image/cr3',
'image/crw',
'image/dcr',
'image/dng',
'image/erf',
'image/fff',
'image/iiq',
'image/k25',
'image/kdc',
'image/mrw',
'image/nef',
'image/nrw',
'image/orf',
'image/ori',
'image/pef',
'image/psd',
'image/raf',
'image/raw',
'image/rw2',
'image/rwl',
'image/sr2',
'image/srf',
'image/srw',
'image/x3f',
]);

export function isRawImage(asset: AssetResponseDto): boolean {
return supportedRawImageMimeTypes.has(asset.originalMimeType!);
}

export const getAssetType = (type: AssetTypeEnum) => {
switch (type) {
case 'IMAGE': {
Expand Down

0 comments on commit f2b631a

Please sign in to comment.