[dyad] Added image scaling and position controls - wrote 2 file(s)
This commit is contained in:
@@ -4,8 +4,6 @@ import { useState, useRef, ChangeEvent, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
@@ -31,6 +29,7 @@ import {
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { ObjectPositionControl } from "./object-position-control";
|
||||
|
||||
export function ImageConverter() {
|
||||
const [images, setImages] = useState<File[]>([]);
|
||||
@@ -47,6 +46,9 @@ export function ImageConverter() {
|
||||
const [counterStart, setCounterStart] = useState<number>(1);
|
||||
const [counterDigits, setCounterDigits] = useState<number>(3);
|
||||
|
||||
const [scaleMode, setScaleMode] = useState<'fill' | 'cover' | 'contain'>('fill');
|
||||
const [objectPosition, setObjectPosition] = useState<string>('center center');
|
||||
|
||||
const [isConverting, setIsConverting] = useState(false);
|
||||
const [convertingIndex, setConvertingIndex] = useState<number | null>(null);
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
||||
@@ -167,7 +169,49 @@ export function ImageConverter() {
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
|
||||
const sWidth = img.naturalWidth;
|
||||
const sHeight = img.naturalHeight;
|
||||
const dWidth = targetWidth;
|
||||
const dHeight = targetHeight;
|
||||
|
||||
if (scaleMode === 'fill' || !width || !height) {
|
||||
ctx.drawImage(img, 0, 0, dWidth, dHeight);
|
||||
} else {
|
||||
const sourceRatio = sWidth / sHeight;
|
||||
const targetRatio = dWidth / dHeight;
|
||||
let sx = 0, sy = 0, sRenderWidth = sWidth, sRenderHeight = sHeight;
|
||||
let dx = 0, dy = 0, dRenderWidth = dWidth, dRenderHeight = dHeight;
|
||||
const [hPos, vPos] = objectPosition.split(' ');
|
||||
|
||||
if (scaleMode === 'cover') {
|
||||
if (sourceRatio > targetRatio) {
|
||||
sRenderHeight = sHeight;
|
||||
sRenderWidth = sHeight * targetRatio;
|
||||
if (hPos === 'center') sx = (sWidth - sRenderWidth) / 2;
|
||||
if (hPos === 'right') sx = sWidth - sRenderWidth;
|
||||
} else {
|
||||
sRenderWidth = sWidth;
|
||||
sRenderHeight = sWidth / targetRatio;
|
||||
if (vPos === 'center') sy = (sHeight - sRenderHeight) / 2;
|
||||
if (vPos === 'bottom') sy = sHeight - sRenderHeight;
|
||||
}
|
||||
ctx.drawImage(img, sx, sy, sRenderWidth, sRenderHeight, 0, 0, dWidth, dHeight);
|
||||
} else if (scaleMode === 'contain') {
|
||||
if (sourceRatio > targetRatio) {
|
||||
dRenderWidth = dWidth;
|
||||
dRenderHeight = dWidth / sourceRatio;
|
||||
if (vPos === 'center') dy = (dHeight - dRenderHeight) / 2;
|
||||
if (vPos === 'bottom') dy = dHeight - dRenderHeight;
|
||||
} else {
|
||||
dRenderHeight = dHeight;
|
||||
dRenderWidth = dHeight * sourceRatio;
|
||||
if (hPos === 'center') dx = (dWidth - dRenderWidth) / 2;
|
||||
if (hPos === 'right') dx = dWidth - dRenderWidth;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0, sWidth, sHeight, dx, dy, dRenderWidth, dRenderHeight);
|
||||
}
|
||||
}
|
||||
|
||||
const mimeType = `image/${format}`;
|
||||
const dataUrl = canvas.toDataURL(mimeType, format === 'png' ? undefined : quality / 100);
|
||||
const link = document.createElement("a");
|
||||
@@ -250,7 +294,7 @@ export function ImageConverter() {
|
||||
<div className="text-left">
|
||||
<h3 className="text-lg font-medium leading-none">Image Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Adjust resolution for all uploaded images.
|
||||
Adjust resolution and scaling for all images.
|
||||
</p>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
@@ -265,6 +309,23 @@ export function ImageConverter() {
|
||||
<Input id="height" type="number" placeholder="Original" value={height} onChange={(e) => setHeight(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2">
|
||||
<Label htmlFor="scale-mode">Scaling</Label>
|
||||
<Select value={scaleMode} onValueChange={(value: 'fill' | 'cover' | 'contain') => setScaleMode(value)}>
|
||||
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="fill">Fill (stretch to fit)</SelectItem>
|
||||
<SelectItem value="cover">Cover (crop to fit)</SelectItem>
|
||||
<SelectItem value="contain">Contain (letterbox)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{scaleMode !== 'fill' && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<Label>Position</Label>
|
||||
<ObjectPositionControl value={objectPosition} onChange={(pos) => setObjectPosition(pos)} />
|
||||
</div>
|
||||
)}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user