mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add AVIF output format support across 6 remaining tools (#85)
Closes #73 AVIF was already supported in the core engine, convert, compress, optimize-for-web, upscale, erase-object, svg-to-raster, and pdf-to-image tools. This adds AVIF as an output format option to the 6 tools that were missing it: split, collage, stitch, image-to-base64, noise-removal, and red-eye-removal. For each tool, both the frontend format selector (with quality slider for AVIF's lossy encoding) and the backend Zod schema + Sharp .avif() encoding were updated. AVIF defaults: quality from the user slider, effort 4 (balanced encode speed). Also fixes pre-existing Biome formatting violations in 5 files that were blocking a clean lint pass.
This commit is contained in:
@@ -332,7 +332,7 @@ const settingsSchema = z.object({
|
|||||||
cornerRadius: z.number().min(0).max(500).default(0),
|
cornerRadius: z.number().min(0).max(500).default(0),
|
||||||
backgroundColor: z.string().default("#FFFFFF"),
|
backgroundColor: z.string().default("#FFFFFF"),
|
||||||
aspectRatio: z.string().default("free"),
|
aspectRatio: z.string().default("free"),
|
||||||
outputFormat: z.enum(["png", "jpeg", "webp"]).default("png"),
|
outputFormat: z.enum(["png", "jpeg", "webp", "avif"]).default("png"),
|
||||||
quality: z.number().min(1).max(100).default(90),
|
quality: z.number().min(1).max(100).default(90),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -633,6 +633,10 @@ export function registerCollage(app: FastifyInstance) {
|
|||||||
pipeline = pipeline.webp({ quality: settings.quality });
|
pipeline = pipeline.webp({ quality: settings.quality });
|
||||||
outputExt = "webp";
|
outputExt = "webp";
|
||||||
break;
|
break;
|
||||||
|
case "avif":
|
||||||
|
pipeline = pipeline.avif({ quality: settings.quality, effort: 4 });
|
||||||
|
outputExt = "avif";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
pipeline = pipeline.png();
|
pipeline = pipeline.png();
|
||||||
outputExt = "png";
|
outputExt = "png";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|||||||
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
import { ensureSharpCompat } from "../../lib/heic-converter.js";
|
||||||
|
|
||||||
const settingsSchema = z.object({
|
const settingsSchema = z.object({
|
||||||
outputFormat: z.enum(["original", "jpeg", "png", "webp"]).default("original"),
|
outputFormat: z.enum(["original", "jpeg", "png", "webp", "avif"]).default("original"),
|
||||||
quality: z.number().int().min(1).max(100).default(80),
|
quality: z.number().int().min(1).max(100).default(80),
|
||||||
maxWidth: z.number().int().min(0).default(0),
|
maxWidth: z.number().int().min(0).default(0),
|
||||||
maxHeight: z.number().int().min(0).default(0),
|
maxHeight: z.number().int().min(0).default(0),
|
||||||
@@ -140,6 +140,10 @@ export function registerImageToBase64(app: FastifyInstance) {
|
|||||||
outputBuffer = await pipeline.webp({ quality: opts.quality }).toBuffer();
|
outputBuffer = await pipeline.webp({ quality: opts.quality }).toBuffer();
|
||||||
mimeType = "image/webp";
|
mimeType = "image/webp";
|
||||||
break;
|
break;
|
||||||
|
case "avif":
|
||||||
|
outputBuffer = await pipeline.avif({ quality: opts.quality, effort: 4 }).toBuffer();
|
||||||
|
mimeType = "image/avif";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
outputBuffer = await pipeline.toBuffer();
|
outputBuffer = await pipeline.toBuffer();
|
||||||
mimeType = detectMimeType(ext);
|
mimeType = detectMimeType(ext);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const settingsSchema = z.object({
|
|||||||
strength: z.union([z.number(), z.string()]).transform(Number).default(50),
|
strength: z.union([z.number(), z.string()]).transform(Number).default(50),
|
||||||
detailPreservation: z.union([z.number(), z.string()]).transform(Number).default(50),
|
detailPreservation: z.union([z.number(), z.string()]).transform(Number).default(50),
|
||||||
colorNoise: z.union([z.number(), z.string()]).transform(Number).default(30),
|
colorNoise: z.union([z.number(), z.string()]).transform(Number).default(30),
|
||||||
format: z.enum(["original", "png", "jpeg", "webp"]).default("original"),
|
format: z.enum(["original", "png", "jpeg", "webp", "avif"]).default("original"),
|
||||||
quality: z.union([z.number(), z.string()]).transform(Number).default(90),
|
quality: z.union([z.number(), z.string()]).transform(Number).default(90),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ export function registerNoiseRemoval(app: FastifyInstance) {
|
|||||||
strength: z.union([z.number(), z.string()]).transform(Number).default(50),
|
strength: z.union([z.number(), z.string()]).transform(Number).default(50),
|
||||||
detailPreservation: z.union([z.number(), z.string()]).transform(Number).default(50),
|
detailPreservation: z.union([z.number(), z.string()]).transform(Number).default(50),
|
||||||
colorNoise: z.union([z.number(), z.string()]).transform(Number).default(30),
|
colorNoise: z.union([z.number(), z.string()]).transform(Number).default(30),
|
||||||
format: z.enum(["original", "png", "jpeg", "webp"]).default("original"),
|
format: z.enum(["original", "png", "jpeg", "webp", "avif"]).default("original"),
|
||||||
quality: z.union([z.number(), z.string()]).transform(Number).default(90),
|
quality: z.union([z.number(), z.string()]).transform(Number).default(90),
|
||||||
}),
|
}),
|
||||||
process: async (inputBuffer, settings, filename) => {
|
process: async (inputBuffer, settings, filename) => {
|
||||||
@@ -188,6 +188,7 @@ export function registerNoiseRemoval(app: FastifyInstance) {
|
|||||||
jpeg: "image/jpeg",
|
jpeg: "image/jpeg",
|
||||||
jpg: "image/jpeg",
|
jpg: "image/jpeg",
|
||||||
webp: "image/webp",
|
webp: "image/webp",
|
||||||
|
avif: "image/avif",
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
buffer: result.buffer,
|
buffer: result.buffer,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const settingsSchema = z.object({
|
|||||||
rows: z.number().min(1).max(100).default(3),
|
rows: z.number().min(1).max(100).default(3),
|
||||||
tileWidth: z.number().min(10).optional(),
|
tileWidth: z.number().min(10).optional(),
|
||||||
tileHeight: z.number().min(10).optional(),
|
tileHeight: z.number().min(10).optional(),
|
||||||
outputFormat: z.enum(["original", "png", "jpg", "webp"]).default("original"),
|
outputFormat: z.enum(["original", "png", "jpg", "webp", "avif"]).default("original"),
|
||||||
quality: z.number().min(1).max(100).default(90),
|
quality: z.number().min(1).max(100).default(90),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ function resolveOutputFormat(
|
|||||||
png: { sharpFormat: "png", ext: ".png" },
|
png: { sharpFormat: "png", ext: ".png" },
|
||||||
jpg: { sharpFormat: "jpeg", ext: ".jpg" },
|
jpg: { sharpFormat: "jpeg", ext: ".jpg" },
|
||||||
webp: { sharpFormat: "webp", ext: ".webp" },
|
webp: { sharpFormat: "webp", ext: ".webp" },
|
||||||
|
avif: { sharpFormat: "avif", ext: ".avif" },
|
||||||
};
|
};
|
||||||
return map[outputFormat] ?? { sharpFormat: null, ext: originalExt };
|
return map[outputFormat] ?? { sharpFormat: null, ext: originalExt };
|
||||||
}
|
}
|
||||||
@@ -137,9 +138,12 @@ export function registerSplit(app: FastifyInstance) {
|
|||||||
let pipeline = sharp(fileBuffer).extract({ left, top, width: w, height: h });
|
let pipeline = sharp(fileBuffer).extract({ left, top, width: w, height: h });
|
||||||
if (sharpFormat) {
|
if (sharpFormat) {
|
||||||
const formatOpts: Record<string, unknown> = {};
|
const formatOpts: Record<string, unknown> = {};
|
||||||
if (sharpFormat === "jpeg" || sharpFormat === "webp") {
|
if (sharpFormat === "jpeg" || sharpFormat === "webp" || sharpFormat === "avif") {
|
||||||
formatOpts.quality = settings.quality;
|
formatOpts.quality = settings.quality;
|
||||||
}
|
}
|
||||||
|
if (sharpFormat === "avif") {
|
||||||
|
formatOpts.effort = 4;
|
||||||
|
}
|
||||||
pipeline = pipeline.toFormat(sharpFormat, formatOpts);
|
pipeline = pipeline.toFormat(sharpFormat, formatOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +224,12 @@ export function registerSplit(app: FastifyInstance) {
|
|||||||
let pipeline = sharp(inputBuffer).extract({ left, top, width: w, height: h });
|
let pipeline = sharp(inputBuffer).extract({ left, top, width: w, height: h });
|
||||||
if (sharpFormat) {
|
if (sharpFormat) {
|
||||||
const formatOpts: Record<string, unknown> = {};
|
const formatOpts: Record<string, unknown> = {};
|
||||||
if (sharpFormat === "jpeg" || sharpFormat === "webp") {
|
if (sharpFormat === "jpeg" || sharpFormat === "webp" || sharpFormat === "avif") {
|
||||||
formatOpts.quality = settings.quality;
|
formatOpts.quality = settings.quality;
|
||||||
}
|
}
|
||||||
|
if (sharpFormat === "avif") {
|
||||||
|
formatOpts.effort = 4;
|
||||||
|
}
|
||||||
pipeline = pipeline.toFormat(sharpFormat, formatOpts);
|
pipeline = pipeline.toFormat(sharpFormat, formatOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const settingsSchema = z.object({
|
|||||||
.string()
|
.string()
|
||||||
.regex(/^#[0-9a-fA-F]{6}$/)
|
.regex(/^#[0-9a-fA-F]{6}$/)
|
||||||
.default("#FFFFFF"),
|
.default("#FFFFFF"),
|
||||||
format: z.enum(["png", "jpeg", "webp"]).default("png"),
|
format: z.enum(["png", "jpeg", "webp", "avif"]).default("png"),
|
||||||
quality: z.number().min(1).max(100).default(90),
|
quality: z.number().min(1).max(100).default(90),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -200,6 +200,8 @@ export function registerStitch(app: FastifyInstance) {
|
|||||||
pipeline = pipeline.jpeg({ quality: settings.quality });
|
pipeline = pipeline.jpeg({ quality: settings.quality });
|
||||||
} else if (settings.format === "webp") {
|
} else if (settings.format === "webp") {
|
||||||
pipeline = pipeline.webp({ quality: settings.quality });
|
pipeline = pipeline.webp({ quality: settings.quality });
|
||||||
|
} else if (settings.format === "avif") {
|
||||||
|
pipeline = pipeline.avif({ quality: settings.quality, effort: 4 });
|
||||||
} else {
|
} else {
|
||||||
pipeline = pipeline.png();
|
pipeline = pipeline.png();
|
||||||
}
|
}
|
||||||
@@ -230,6 +232,8 @@ export function registerStitch(app: FastifyInstance) {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
} else if (settings.format === "webp") {
|
} else if (settings.format === "webp") {
|
||||||
result = await sharp(result).webp({ quality: settings.quality }).toBuffer();
|
result = await sharp(result).webp({ quality: settings.quality }).toBuffer();
|
||||||
|
} else if (settings.format === "avif") {
|
||||||
|
result = await sharp(result).avif({ quality: settings.quality, effort: 4 }).toBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const OUTPUT_FORMATS: { value: OutputFormat; label: string }[] = [
|
|||||||
{ value: "png", label: "PNG" },
|
{ value: "png", label: "PNG" },
|
||||||
{ value: "jpeg", label: "JPEG" },
|
{ value: "jpeg", label: "JPEG" },
|
||||||
{ value: "webp", label: "WebP" },
|
{ value: "webp", label: "WebP" },
|
||||||
|
{ value: "avif", label: "AVIF" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const BG_PRESETS = [
|
const BG_PRESETS = [
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const OUTPUT_FORMATS = [
|
|||||||
{ value: "jpeg", label: "JPEG" },
|
{ value: "jpeg", label: "JPEG" },
|
||||||
{ value: "png", label: "PNG" },
|
{ value: "png", label: "PNG" },
|
||||||
{ value: "webp", label: "WebP" },
|
{ value: "webp", label: "WebP" },
|
||||||
|
{ value: "avif", label: "AVIF" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export function ImageToBase64Settings() {
|
export function ImageToBase64Settings() {
|
||||||
@@ -68,7 +69,7 @@ export function ImageToBase64Settings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasFiles = files.length > 0;
|
const hasFiles = files.length > 0;
|
||||||
const showQuality = outputFormat === "jpeg" || outputFormat === "webp";
|
const showQuality = outputFormat === "jpeg" || outputFormat === "webp" || outputFormat === "avif";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const TIERS: { id: Tier; label: string; desc: string }[] = [
|
|||||||
{ id: "maximum", label: "Maximum", desc: "Best AI model, slowest" },
|
{ id: "maximum", label: "Maximum", desc: "Best AI model, slowest" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const LOSSY_FORMATS = new Set(["jpeg", "webp"]);
|
const LOSSY_FORMATS = new Set(["jpeg", "webp", "avif"]);
|
||||||
|
|
||||||
export interface NoiseRemovalControlsProps {
|
export interface NoiseRemovalControlsProps {
|
||||||
settings?: Record<string, unknown>;
|
settings?: Record<string, unknown>;
|
||||||
@@ -28,7 +28,7 @@ export function NoiseRemovalControls({
|
|||||||
const [strength, setStrength] = useState(50);
|
const [strength, setStrength] = useState(50);
|
||||||
const [detailPreservation, setDetailPreservation] = useState(50);
|
const [detailPreservation, setDetailPreservation] = useState(50);
|
||||||
const [colorNoise, setColorNoise] = useState(30);
|
const [colorNoise, setColorNoise] = useState(30);
|
||||||
const [outputFormat, setOutputFormat] = useState<"original" | "png" | "jpeg" | "webp">(
|
const [outputFormat, setOutputFormat] = useState<"original" | "png" | "jpeg" | "webp" | "avif">(
|
||||||
"original",
|
"original",
|
||||||
);
|
);
|
||||||
const [quality, setQuality] = useState(90);
|
const [quality, setQuality] = useState(90);
|
||||||
@@ -44,7 +44,7 @@ export function NoiseRemovalControls({
|
|||||||
setDetailPreservation(Number(initialSettings.detailPreservation));
|
setDetailPreservation(Number(initialSettings.detailPreservation));
|
||||||
if (initialSettings.colorNoise != null) setColorNoise(Number(initialSettings.colorNoise));
|
if (initialSettings.colorNoise != null) setColorNoise(Number(initialSettings.colorNoise));
|
||||||
if (initialSettings.format != null)
|
if (initialSettings.format != null)
|
||||||
setOutputFormat(initialSettings.format as "original" | "png" | "jpeg" | "webp");
|
setOutputFormat(initialSettings.format as "original" | "png" | "jpeg" | "webp" | "avif");
|
||||||
if (initialSettings.quality != null) setQuality(Number(initialSettings.quality));
|
if (initialSettings.quality != null) setQuality(Number(initialSettings.quality));
|
||||||
}, [initialSettings]);
|
}, [initialSettings]);
|
||||||
|
|
||||||
@@ -164,8 +164,8 @@ export function NoiseRemovalControls({
|
|||||||
{/* Output format */}
|
{/* Output format */}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground mb-1">Output Format</p>
|
<p className="text-xs text-muted-foreground mb-1">Output Format</p>
|
||||||
<div className="grid grid-cols-4 gap-1">
|
<div className="grid grid-cols-5 gap-1">
|
||||||
{(["original", "png", "jpeg", "webp"] as const).map((f) => (
|
{(["original", "png", "jpeg", "webp", "avif"] as const).map((f) => (
|
||||||
<button
|
<button
|
||||||
key={f}
|
key={f}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ProgressCard } from "@/components/common/progress-card";
|
|||||||
import { useToolProcessor } from "@/hooks/use-tool-processor";
|
import { useToolProcessor } from "@/hooks/use-tool-processor";
|
||||||
import { useFileStore } from "@/stores/file-store";
|
import { useFileStore } from "@/stores/file-store";
|
||||||
|
|
||||||
const LOSSY_FORMATS = new Set(["jpeg", "webp"]);
|
const LOSSY_FORMATS = new Set(["jpeg", "webp", "avif"]);
|
||||||
|
|
||||||
export interface RedEyeRemovalControlsProps {
|
export interface RedEyeRemovalControlsProps {
|
||||||
settings?: Record<string, unknown>;
|
settings?: Record<string, unknown>;
|
||||||
@@ -17,7 +17,7 @@ export function RedEyeRemovalControls({
|
|||||||
}: RedEyeRemovalControlsProps) {
|
}: RedEyeRemovalControlsProps) {
|
||||||
const [sensitivity, setSensitivity] = useState(50);
|
const [sensitivity, setSensitivity] = useState(50);
|
||||||
const [strength, setStrength] = useState(70);
|
const [strength, setStrength] = useState(70);
|
||||||
const [outputFormat, setOutputFormat] = useState<"original" | "png" | "jpeg" | "webp">(
|
const [outputFormat, setOutputFormat] = useState<"original" | "png" | "jpeg" | "webp" | "avif">(
|
||||||
"original",
|
"original",
|
||||||
);
|
);
|
||||||
const [quality, setQuality] = useState(90);
|
const [quality, setQuality] = useState(90);
|
||||||
@@ -30,7 +30,7 @@ export function RedEyeRemovalControls({
|
|||||||
if (initialSettings.sensitivity != null) setSensitivity(Number(initialSettings.sensitivity));
|
if (initialSettings.sensitivity != null) setSensitivity(Number(initialSettings.sensitivity));
|
||||||
if (initialSettings.strength != null) setStrength(Number(initialSettings.strength));
|
if (initialSettings.strength != null) setStrength(Number(initialSettings.strength));
|
||||||
if (initialSettings.format != null)
|
if (initialSettings.format != null)
|
||||||
setOutputFormat(initialSettings.format as "original" | "png" | "jpeg" | "webp");
|
setOutputFormat(initialSettings.format as "original" | "png" | "jpeg" | "webp" | "avif");
|
||||||
if (initialSettings.quality != null) setQuality(Number(initialSettings.quality));
|
if (initialSettings.quality != null) setQuality(Number(initialSettings.quality));
|
||||||
}, [initialSettings]);
|
}, [initialSettings]);
|
||||||
|
|
||||||
@@ -101,8 +101,8 @@ export function RedEyeRemovalControls({
|
|||||||
{/* Output format */}
|
{/* Output format */}
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground mb-1">Output Format</p>
|
<p className="text-xs text-muted-foreground mb-1">Output Format</p>
|
||||||
<div className="grid grid-cols-4 gap-1">
|
<div className="grid grid-cols-5 gap-1">
|
||||||
{(["original", "png", "jpeg", "webp"] as const).map((f) => (
|
{(["original", "png", "jpeg", "webp", "avif"] as const).map((f) => (
|
||||||
<button
|
<button
|
||||||
key={f}
|
key={f}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ const OUTPUT_FORMATS = [
|
|||||||
{ value: "png", label: "PNG" },
|
{ value: "png", label: "PNG" },
|
||||||
{ value: "jpg", label: "JPG" },
|
{ value: "jpg", label: "JPG" },
|
||||||
{ value: "webp", label: "WebP" },
|
{ value: "webp", label: "WebP" },
|
||||||
|
{ value: "avif", label: "AVIF" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const LOSSY_FORMATS = new Set(["jpg", "webp"]);
|
const LOSSY_FORMATS = new Set(["jpg", "webp", "avif"]);
|
||||||
|
|
||||||
export function SplitSettings() {
|
export function SplitSettings() {
|
||||||
const { files, processing: fileStoreProcessing } = useFileStore();
|
const { files, processing: fileStoreProcessing } = useFileStore();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useFileStore } from "@/stores/file-store";
|
|||||||
type Direction = "horizontal" | "vertical" | "grid";
|
type Direction = "horizontal" | "vertical" | "grid";
|
||||||
type ResizeMode = "fit" | "original" | "stretch" | "crop";
|
type ResizeMode = "fit" | "original" | "stretch" | "crop";
|
||||||
type Alignment = "start" | "center" | "end";
|
type Alignment = "start" | "center" | "end";
|
||||||
type OutputFormat = "png" | "jpeg" | "webp";
|
type OutputFormat = "png" | "jpeg" | "webp" | "avif";
|
||||||
|
|
||||||
export function StitchSettings() {
|
export function StitchSettings() {
|
||||||
const { files, processing, error, setProcessing, setError, setProcessedUrl, setSizes, setJobId } =
|
const { files, processing, error, setProcessing, setError, setProcessedUrl, setSizes, setJobId } =
|
||||||
@@ -227,7 +227,7 @@ export function StitchSettings() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Format</p>
|
<p className="text-xs text-muted-foreground">Format</p>
|
||||||
<div className="grid grid-cols-3 gap-1 mt-1">
|
<div className="grid grid-cols-3 gap-1 mt-1">
|
||||||
{(["png", "jpeg", "webp"] as const).map((f) => (
|
{(["png", "jpeg", "webp", "avif"] as const).map((f) => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={f}
|
key={f}
|
||||||
@@ -240,7 +240,7 @@ export function StitchSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(format === "jpeg" || format === "webp") && (
|
{(format === "jpeg" || format === "webp" || format === "avif") && (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<label htmlFor="stitch-quality" className="text-xs text-muted-foreground">
|
<label htmlFor="stitch-quality" className="text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { COLLAGE_TEMPLATES, getDefaultTemplate } from "@/lib/collage-templates";
|
|||||||
import { fetchDecodedPreview, needsServerPreview, revokePreviewUrl } from "@/lib/image-preview";
|
import { fetchDecodedPreview, needsServerPreview, revokePreviewUrl } from "@/lib/image-preview";
|
||||||
|
|
||||||
export type AspectRatio = "free" | "1:1" | "4:3" | "3:2" | "16:9" | "9:16" | "4:5";
|
export type AspectRatio = "free" | "1:1" | "4:3" | "3:2" | "16:9" | "9:16" | "4:5";
|
||||||
export type OutputFormat = "png" | "jpeg" | "webp";
|
export type OutputFormat = "png" | "jpeg" | "webp" | "avif";
|
||||||
export type Phase = "upload" | "editing" | "processing" | "result";
|
export type Phase = "upload" | "editing" | "processing" | "result";
|
||||||
|
|
||||||
export interface CollageImage {
|
export interface CollageImage {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface SplitState {
|
|||||||
rows: number;
|
rows: number;
|
||||||
tileWidth: number;
|
tileWidth: number;
|
||||||
tileHeight: number;
|
tileHeight: number;
|
||||||
outputFormat: "original" | "png" | "jpg" | "webp";
|
outputFormat: "original" | "png" | "jpg" | "webp" | "avif";
|
||||||
quality: number;
|
quality: number;
|
||||||
|
|
||||||
// Image dimensions (set when image loads in the canvas)
|
// Image dimensions (set when image loads in the canvas)
|
||||||
@@ -36,7 +36,7 @@ interface SplitState {
|
|||||||
setRows: (n: number) => void;
|
setRows: (n: number) => void;
|
||||||
setTileWidth: (n: number) => void;
|
setTileWidth: (n: number) => void;
|
||||||
setTileHeight: (n: number) => void;
|
setTileHeight: (n: number) => void;
|
||||||
setOutputFormat: (f: "original" | "png" | "jpg" | "webp") => void;
|
setOutputFormat: (f: "original" | "png" | "jpg" | "webp" | "avif") => void;
|
||||||
setQuality: (q: number) => void;
|
setQuality: (q: number) => void;
|
||||||
setImageDimensions: (d: { width: number; height: number } | null) => void;
|
setImageDimensions: (d: { width: number; height: number } | null) => void;
|
||||||
setProcessing: (p: boolean) => void;
|
setProcessing: (p: boolean) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user