diff --git a/src/frontend/containers/ContentView/GalleryItem.tsx b/src/frontend/containers/ContentView/GalleryItem.tsx index 1205ff40..a6373bfc 100644 --- a/src/frontend/containers/ContentView/GalleryItem.tsx +++ b/src/frontend/containers/ContentView/GalleryItem.tsx @@ -151,12 +151,16 @@ export const Thumbnail = observer(({ file, mounted, forceNoThumbnail }: ItemProp return ; } else if (imageSource.tag === 'ready') { if ('ok' in imageSource.value) { + const is_lowres = file.width < 320 || file.height < 320; return ( ); } else { diff --git a/src/frontend/containers/ContentView/SlideMode/ZoomPan.tsx b/src/frontend/containers/ContentView/SlideMode/ZoomPan.tsx index 0febf9d1..e41cd935 100644 --- a/src/frontend/containers/ContentView/SlideMode/ZoomPan.tsx +++ b/src/frontend/containers/ContentView/SlideMode/ZoomPan.tsx @@ -22,6 +22,7 @@ import { tryPreventDefault, Vec2, } from './utils'; +import { UpscaleMode } from '../../../stores/UiStore'; const OVERZOOM_TOLERANCE = 0.05; const DOUBLE_TAP_THRESHOLD = 250; @@ -41,6 +42,7 @@ export interface ZoomPanProps { imageDimension: Dimension; containerDimension: Dimension; onClose?: () => void; + upscaleMode: UpscaleMode; transitionStart?: Transform; transitionEnd?: Transform; @@ -296,7 +298,7 @@ export default class ZoomPan extends React.Component onWheel: this.handleMouseWheel, onDragStart: tryPreventDefault, onContextMenu: tryPreventDefault, - style: imageStyle(this.state), + style: imageStyle(this.state, this.props.upscaleMode), })} ); @@ -485,9 +487,10 @@ export const CONTAINER_DEFAULT_STYLE = { margin: 'auto', }; -function imageStyle({ top, left, scale }: ZoomPanState): CSSProperties { +function imageStyle({ top, left, scale }: ZoomPanState, upscaleMode: UpscaleMode): CSSProperties { return { transform: `translate3d(${Math.trunc(left)}px, ${Math.trunc(top)}px, 0) scale(${scale})`, + imageRendering: scale >= 2 && upscaleMode === 'pixelated' ? 'pixelated' : undefined, }; } diff --git a/src/frontend/containers/ContentView/SlideMode/index.tsx b/src/frontend/containers/ContentView/SlideMode/index.tsx index 437be9d4..d0069415 100644 --- a/src/frontend/containers/ContentView/SlideMode/index.tsx +++ b/src/frontend/containers/ContentView/SlideMode/index.tsx @@ -14,6 +14,7 @@ import { CommandDispatcher } from '../Commands'; import { ContentRect } from '../utils'; import ZoomPan, { CONTAINER_DEFAULT_STYLE, SlideTransform } from '../SlideMode/ZoomPan'; import { createDimension, createTransform, Vec2 } from './utils'; +import { UpscaleMode } from 'src/frontend/stores/UiStore'; const SlideMode = observer(({ contentRect }: { contentRect: ContentRect }) => { const { uiStore } = useStore(); @@ -177,6 +178,7 @@ const SlideView = observer(({ width, height }: SlideViewProps) => { transitionStart={transitionStart} transitionEnd={uiStore.isSlideMode ? undefined : transitionStart} onClose={uiStore.disableSlideMode} + upscaleMode={uiStore.upscaleMode} /> )} void; + upscaleMode: UpscaleMode; } const ZoomableImage: React.FC = ({ @@ -207,6 +210,7 @@ const ZoomableImage: React.FC = ({ transitionStart, transitionEnd, onClose, + upscaleMode, }: ZoomableImageProps) => { const { imageLoader } = useStore(); const { absolutePath, width: imgWidth, height: imgHeight } = file; @@ -277,6 +281,7 @@ const ZoomableImage: React.FC = ({ transitionStart={transitionStart} transitionEnd={transitionEnd} onClose={onClose} + upscaleMode={upscaleMode} > {(props) => ( { ); }; -export const SlideFileViewerMenuItems = ({ file }: { file: ClientFile }) => { +export const SlideFileViewerMenuItems = observer(({ file }: { file: ClientFile }) => { const { uiStore } = useStore(); const handlePreviewWindow = () => { @@ -175,9 +175,22 @@ export const SlideFileViewerMenuItems = ({ file }: { file: ClientFile }) => { text="Open In Preview Window" icon={IconSet.PREVIEW} /> + + + + + ); -}; +}); export const ExternalAppMenuItems = observer(({ file }: { file: ClientFile }) => { const { uiStore } = useStore(); diff --git a/src/frontend/containers/Settings/index.tsx b/src/frontend/containers/Settings/index.tsx index a5a298d3..d8825fc5 100644 --- a/src/frontend/containers/Settings/index.tsx +++ b/src/frontend/containers/Settings/index.tsx @@ -86,6 +86,23 @@ const Appearance = observer(() => { +
+ + + + +
+

Thumbnail

diff --git a/src/frontend/stores/UiStore.ts b/src/frontend/stores/UiStore.ts index 182e5b56..e9da25ba 100644 --- a/src/frontend/stores/UiStore.ts +++ b/src/frontend/stores/UiStore.ts @@ -20,6 +20,7 @@ export const enum ViewMethod { } export type ThumbnailSize = 'small' | 'medium' | 'large' | number; type ThumbnailShape = 'square' | 'letterbox'; +export type UpscaleMode = 'smooth' | 'pixelated'; export const PREFERENCES_STORAGE_KEY = 'preferences'; export interface IHotkeyMap { @@ -100,6 +101,7 @@ type PersistentPreferenceFields = | 'method' | 'thumbnailSize' | 'thumbnailShape' + | 'upscaleMode' | 'hotkeyMap' | 'isThumbnailTagOverlayEnabled' | 'isThumbnailFilenameOverlayEnabled' @@ -148,6 +150,7 @@ class UiStore { @observable firstItem: number = 0; @observable thumbnailSize: ThumbnailSize | number = 'medium'; @observable thumbnailShape: ThumbnailShape = 'square'; + @observable upscaleMode: UpscaleMode = 'smooth'; @observable isToolbarTagPopoverOpen: boolean = false; /** Dialog for removing unlinked files from Allusion's database */ @@ -216,6 +219,14 @@ class UiStore { this.setThumbnailShape('letterbox'); } + @action.bound setUpscaleModeSmooth() { + this.setUpscaleMode('smooth'); + } + + @action.bound setUpscaleModePixelated() { + this.setUpscaleMode('pixelated'); + } + @action.bound setFirstItem(index: number = 0) { if (isFinite(index) && index < this.rootStore.fileStore.fileList.length) { this.firstItem = index; @@ -812,6 +823,9 @@ class UiStore { if (prefs.thumbnailShape) { this.setThumbnailShape(prefs.thumbnailShape); } + if (prefs.upscaleMode) { + this.setUpscaleMode(prefs.upscaleMode); + } this.isThumbnailTagOverlayEnabled = Boolean(prefs.isThumbnailTagOverlayEnabled ?? true); this.isThumbnailFilenameOverlayEnabled = Boolean(prefs.isThumbnailFilenameOverlayEnabled ?? false); // eslint-disable-line prettier/prettier this.isThumbnailResolutionOverlayEnabled = Boolean(prefs.isThumbnailResolutionOverlayEnabled ?? false); // eslint-disable-line prettier/prettier @@ -866,6 +880,7 @@ class UiStore { method: this.method, thumbnailSize: this.thumbnailSize, thumbnailShape: this.thumbnailShape, + upscaleMode: this.upscaleMode, hotkeyMap: { ...this.hotkeyMap }, isThumbnailFilenameOverlayEnabled: this.isThumbnailFilenameOverlayEnabled, isThumbnailTagOverlayEnabled: this.isThumbnailTagOverlayEnabled, @@ -928,6 +943,10 @@ class UiStore { @action private setThumbnailShape(shape: ThumbnailShape) { this.thumbnailShape = shape; } + + @action private setUpscaleMode(mode: UpscaleMode) { + this.upscaleMode = mode; + } } export default UiStore;