Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add upscale smooth/pixelated option #531

Merged
merged 3 commits into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/frontend/containers/ContentView/GalleryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,16 @@ export const Thumbnail = observer(({ file, mounted, forceNoThumbnail }: ItemProp
return <span className="image-loading" />;
} else if (imageSource.tag === 'ready') {
if ('ok' in imageSource.value) {
const is_lowres = file.width < 320 || file.height < 320;
return (
<img
src={encodeFilePath(imageSource.value.ok)}
alt=""
data-file-id={file.id}
onError={handleImageError}
style={
is_lowres && uiStore.upscaleMode == 'pixelated' ? { imageRendering: 'pixelated' } : {}
}
/>
);
} else {
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/containers/ContentView/SlideMode/ZoomPan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
tryPreventDefault,
Vec2,
} from './utils';
import { UpscaleMode } from '../../../stores/UiStore';

const OVERZOOM_TOLERANCE = 0.05;
const DOUBLE_TAP_THRESHOLD = 250;
Expand All @@ -41,6 +42,7 @@ export interface ZoomPanProps {
imageDimension: Dimension;
containerDimension: Dimension;
onClose?: () => void;
upscaleMode: UpscaleMode;

transitionStart?: Transform;
transitionEnd?: Transform;
Expand Down Expand Up @@ -296,7 +298,7 @@ export default class ZoomPan extends React.Component<ZoomPanProps, ZoomPanState>
onWheel: this.handleMouseWheel,
onDragStart: tryPreventDefault,
onContextMenu: tryPreventDefault,
style: imageStyle(this.state),
style: imageStyle(this.state, this.props.upscaleMode),
})}
</div>
);
Expand Down Expand Up @@ -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,
};
}

Expand Down
5 changes: 5 additions & 0 deletions src/frontend/containers/ContentView/SlideMode/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -177,6 +178,7 @@ const SlideView = observer(({ width, height }: SlideViewProps) => {
transitionStart={transitionStart}
transitionEnd={uiStore.isSlideMode ? undefined : transitionStart}
onClose={uiStore.disableSlideMode}
upscaleMode={uiStore.upscaleMode}
/>
)}
<NavigationButtons
Expand All @@ -197,6 +199,7 @@ interface ZoomableImageProps {
transitionStart?: SlideTransform;
transitionEnd?: SlideTransform;
onClose: () => void;
upscaleMode: UpscaleMode;
}

const ZoomableImage: React.FC<ZoomableImageProps> = ({
Expand All @@ -207,6 +210,7 @@ const ZoomableImage: React.FC<ZoomableImageProps> = ({
transitionStart,
transitionEnd,
onClose,
upscaleMode,
}: ZoomableImageProps) => {
const { imageLoader } = useStore();
const { absolutePath, width: imgWidth, height: imgHeight } = file;
Expand Down Expand Up @@ -277,6 +281,7 @@ const ZoomableImage: React.FC<ZoomableImageProps> = ({
transitionStart={transitionStart}
transitionEnd={transitionEnd}
onClose={onClose}
upscaleMode={upscaleMode}
>
{(props) => (
<img
Expand Down
19 changes: 16 additions & 3 deletions src/frontend/containers/ContentView/menu-items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { ClientTag } from 'src/entities/Tag';
import { useStore } from 'src/frontend/contexts/StoreContext';
import { IconSet } from 'widgets';
import { MenuItem, MenuSubItem } from 'widgets/menus';
import { MenuItem, MenuRadioItem, MenuSubItem } from 'widgets/menus';
import { LocationTreeItemRevealer } from '../Outliner/LocationsPanel';
import { TagsTreeItemRevealer } from '../Outliner/TagsPanel/TagsTree';
import SysPath from 'path';
Expand Down Expand Up @@ -160,7 +160,7 @@ export const FileViewerMenuItems = ({ file }: { file: ClientFile }) => {
);
};

export const SlideFileViewerMenuItems = ({ file }: { file: ClientFile }) => {
export const SlideFileViewerMenuItems = observer(({ file }: { file: ClientFile }) => {
const { uiStore } = useStore();

const handlePreviewWindow = () => {
Expand All @@ -175,9 +175,22 @@ export const SlideFileViewerMenuItems = ({ file }: { file: ClientFile }) => {
text="Open In Preview Window"
icon={IconSet.PREVIEW}
/>

<MenuSubItem text="Upscale filtering..." icon={IconSet.VIEW_GRID}>
<MenuRadioItem
onClick={uiStore.setUpscaleModeSmooth}
checked={uiStore.upscaleMode === 'smooth'}
text="Smooth"
/>
<MenuRadioItem
onClick={uiStore.setUpscaleModePixelated}
checked={uiStore.upscaleMode === 'pixelated'}
text="Pixelated"
/>
</MenuSubItem>
</>
);
};
});

export const ExternalAppMenuItems = observer(({ file }: { file: ClientFile }) => {
const { uiStore } = useStore();
Expand Down
17 changes: 17 additions & 0 deletions src/frontend/containers/Settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ const Appearance = observer(() => {
</fieldset>
</div>

<div className="input-group">
<RadioGroup name="Picture upscaling">
<Radio
label="Smooth"
checked={uiStore.upscaleMode === 'smooth'}
value="smooth"
onChange={uiStore.setUpscaleModeSmooth}
/>
<Radio
label="Pixelated"
checked={uiStore.upscaleMode === 'pixelated'}
value="pixelated"
onChange={uiStore.setUpscaleModePixelated}
/>
</RadioGroup>
</div>

<h3>Thumbnail</h3>

<div className="input-group">
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/stores/UiStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -100,6 +101,7 @@ type PersistentPreferenceFields =
| 'method'
| 'thumbnailSize'
| 'thumbnailShape'
| 'upscaleMode'
| 'hotkeyMap'
| 'isThumbnailTagOverlayEnabled'
| 'isThumbnailFilenameOverlayEnabled'
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;