diff --git a/backend/src/managers/image/image-converter.service.ts b/backend/src/managers/image/image-converter.service.ts
index 35dfe7e..97455f2 100644
--- a/backend/src/managers/image/image-converter.service.ts
+++ b/backend/src/managers/image/image-converter.service.ts
@@ -72,19 +72,22 @@ export class ImageConverterService {
// Do modifications
if (options.height || options.width) {
- if (options.height && options.width) {
+ if ((options.height && options.width)) {
sharpWrapper.operation('resize', {
width: options.width,
height: options.height,
fit: 'fill',
kernel: 'cubic',
+ withoutEnlargement: options.shrinkonly,
});
} else {
sharpWrapper.operation('resize', {
width: options.width,
height: options.height,
- fit: 'contain',
+ fit: 'inside',
kernel: 'cubic',
+
+ withoutEnlargement: options.shrinkonly,
});
}
}
diff --git a/frontend/src/app/routes/images/images.component.ts b/frontend/src/app/routes/images/images.component.ts
index 93b0cd1..4b42b2a 100644
--- a/frontend/src/app/routes/images/images.component.ts
+++ b/frontend/src/app/routes/images/images.component.ts
@@ -71,7 +71,7 @@ export class ImagesComponent implements OnInit {
getThumbnailUrl(image: EImage) {
return (
- this.imageService.GetImageURL(image.id, ImageFileType.QOI) + '?height=480'
+ this.imageService.GetImageURL(image.id, ImageFileType.QOI) + '?height=480&shrinkonly=yes'
);
}
diff --git a/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.html b/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.html
index 7ca19e8..6c2f41e 100644
--- a/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.html
+++ b/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.html
@@ -68,6 +68,10 @@
+
+ Shrink only
+
+
Greyscale
diff --git a/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.ts b/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.ts
index b70047c..c7cd558 100644
--- a/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.ts
+++ b/frontend/src/app/routes/view/customize-dialog/customize-dialog.component.ts
@@ -32,6 +32,7 @@ export class CustomizeDialogComponent implements OnInit {
public rotate: number;
public flipx: boolean;
public flipy: boolean;
+ public shrinkonly: boolean;
public greyscale: boolean;
public noalpha: boolean;
public negative: boolean;
@@ -64,6 +65,7 @@ export class CustomizeDialogComponent implements OnInit {
quality: this.quality ?? undefined,
flipx: this.flipx,
flipy: this.flipy,
+ shrinkonly: this.shrinkonly,
greyscale: this.greyscale,
noalpha: this.noalpha,
negative: this.negative,
diff --git a/frontend/src/app/routes/view/view.component.ts b/frontend/src/app/routes/view/view.component.ts
index 608c1bc..19b8957 100644
--- a/frontend/src/app/routes/view/view.component.ts
+++ b/frontend/src/app/routes/view/view.component.ts
@@ -80,11 +80,13 @@ export class ViewComponent implements OnInit {
if (HasFailed(metadata))
return this.utilService.quitError(metadata.getReason());
+ // Get width of screen in pixels
+ const width = window.innerWidth * window.devicePixelRatio;
+
// Populate fields with metadata
- this.previewLink = this.imageService.GetImageURL(
- this.id,
- metadata.fileTypes.master,
- );
+ this.previewLink =
+ this.imageService.GetImageURL(this.id, metadata.fileTypes.master) +
+ (width > 1 ? `?width=${width}&shrinkonly=yes` : '');
this.hasOriginal = metadata.fileTypes.original !== undefined;
@@ -162,10 +164,7 @@ export class ViewComponent implements OnInit {
);
}
- this.utilService.showSnackBar(
- 'Image deleted',
- SnackBarType.Success,
- );
+ this.utilService.showSnackBar('Image deleted', SnackBarType.Success);
this.router.navigate(['/']);
}
diff --git a/frontend/src/app/services/api/image.service.ts b/frontend/src/app/services/api/image.service.ts
index 58394d6..d3c0e4c 100644
--- a/frontend/src/app/services/api/image.service.ts
+++ b/frontend/src/app/services/api/image.service.ts
@@ -15,7 +15,13 @@ import { ImageLinks } from 'picsur-shared/dist/dto/image-links.class';
import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import { EImage } from 'picsur-shared/dist/entities/image.entity';
import { AsyncFailable } from 'picsur-shared/dist/types';
-import { Fail, FT, HasFailed, HasSuccess, Open } from 'picsur-shared/dist/types/failable';
+import {
+ Fail,
+ FT,
+ HasFailed,
+ HasSuccess,
+ Open
+} from 'picsur-shared/dist/types/failable';
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
import { ApiService } from './api.service';
import { UserService } from './user.service';
@@ -107,7 +113,9 @@ export class ImageService {
const baseURL = this.location.protocol + '//' + this.location.host;
const extension = FileType2Ext(filetype ?? '');
- return `${baseURL}/i/${image}${HasSuccess(extension) ? '.' + extension : ''}`;
+ return `${baseURL}/i/${image}${
+ HasSuccess(extension) ? '.' + extension : ''
+ }`;
}
public GetImageURLCustomized(
@@ -129,6 +137,8 @@ export class ImageService {
queryParams.push(`rotate=${options.rotate}`);
if (options.flipx !== undefined) queryParams.push(`flipx=${options.flipx}`);
if (options.flipy !== undefined) queryParams.push(`flipy=${options.flipy}`);
+ if (options.shrinkonly !== undefined)
+ queryParams.push(`shrinkonly=${options.shrinkonly}`);
if (options.greyscale !== undefined)
queryParams.push(`greyscale=${options.greyscale}`);
if (options.noalpha !== undefined)
diff --git a/shared/src/dto/api/image.dto.ts b/shared/src/dto/api/image.dto.ts
index cb66797..058b876 100644
--- a/shared/src/dto/api/image.dto.ts
+++ b/shared/src/dto/api/image.dto.ts
@@ -2,15 +2,15 @@ import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity';
import { EUserSchema } from '../../entities/user.entity';
import { createZodDto } from '../../util/create-zod-dto';
-import { ParseBool } from '../../util/parse-simple';
+import { ParseBool, ParseInt } from '../../util/parse-simple';
import { ImageEntryVariant } from '../image-entry-variant.enum';
export const ImageRequestParamsSchema = z
.object({
- height: z.preprocess(Number, z.number().int().min(1).max(32767)),
- width: z.preprocess(Number, z.number().int().min(1).max(32767)),
+ height: z.preprocess(ParseInt, z.number().int().min(1).max(32767)),
+ width: z.preprocess(ParseInt, z.number().int().min(1).max(32767)),
rotate: z.preprocess(
- Number,
+ ParseInt,
z.number().int().multipleOf(90).min(0).max(360),
),
flipx: z.preprocess(ParseBool, z.boolean()),
@@ -18,7 +18,8 @@ export const ImageRequestParamsSchema = z
greyscale: z.preprocess(ParseBool, z.boolean()),
noalpha: z.preprocess(ParseBool, z.boolean()),
negative: z.preprocess(ParseBool, z.boolean()),
- quality: z.preprocess(Number, z.number().int().min(1).max(100)),
+ shrinkonly: z.preprocess(ParseBool, z.boolean()),
+ quality: z.preprocess(ParseInt, z.number().int().min(1).max(100)),
})
.partial();
diff --git a/shared/src/util/parse-simple.ts b/shared/src/util/parse-simple.ts
index 01e59eb..45b9804 100644
--- a/shared/src/util/parse-simple.ts
+++ b/shared/src/util/parse-simple.ts
@@ -14,13 +14,17 @@ export const ParseInt = (
value: unknown,
fallback?: T,
): number | T => {
- if (typeof value === 'number') return value;
+ if (typeof value === 'number') return Math.round(value);
if (typeof value === 'boolean') return value ? 1 : 0;
if (typeof value === 'string') {
- const parsed = parseInt(value);
- if (!isNaN(parsed)) return parsed;
+ const parsed = Number(value);
+ if (!isNaN(parsed)) return Math.round(parsed);
}
- return fallback === undefined ? (null as T) : fallback;
+ return fallback === undefined
+ ? (null as T)
+ : fallback === null
+ ? fallback
+ : Math.round(fallback);
};
export const ParseString = (