mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-12-29 16:16:02 +00:00
feat: svg resize
This commit is contained in:
@@ -9,8 +9,10 @@ import ToolContent from '@components/ToolContent';
|
||||
import { ToolComponentProps } from '@tools/defineTool';
|
||||
import SimpleRadio from '@components/options/SimpleRadio';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import { processImage } from './service';
|
||||
import { InitialValuesType } from './types';
|
||||
|
||||
const initialValues = {
|
||||
const initialValues: InitialValuesType = {
|
||||
resizeMethod: 'pixels' as 'pixels' | 'percentage',
|
||||
dimensionType: 'width' as 'width' | 'height',
|
||||
width: '800',
|
||||
@@ -19,8 +21,6 @@ const initialValues = {
|
||||
maintainAspectRatio: true
|
||||
};
|
||||
|
||||
type InitialValuesType = typeof initialValues;
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
width: Yup.number().when('resizeMethod', {
|
||||
is: 'pixels',
|
||||
@@ -48,82 +48,9 @@ export default function ResizeImage({ title }: ToolComponentProps) {
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
|
||||
const compute = (optionsValues: InitialValuesType, input: any) => {
|
||||
const compute = async (optionsValues: InitialValuesType, input: any) => {
|
||||
if (!input) return;
|
||||
|
||||
const {
|
||||
resizeMethod,
|
||||
dimensionType,
|
||||
width,
|
||||
height,
|
||||
percentage,
|
||||
maintainAspectRatio
|
||||
} = optionsValues;
|
||||
|
||||
const processImage = async (file: File) => {
|
||||
// Create canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx == null) return;
|
||||
|
||||
// Load image
|
||||
const img = new Image();
|
||||
img.src = URL.createObjectURL(file);
|
||||
await img.decode();
|
||||
|
||||
// Calculate new dimensions
|
||||
let newWidth = img.width;
|
||||
let newHeight = img.height;
|
||||
|
||||
if (resizeMethod === 'pixels') {
|
||||
if (dimensionType === 'width') {
|
||||
newWidth = parseInt(width);
|
||||
if (maintainAspectRatio) {
|
||||
newHeight = Math.round((newWidth / img.width) * img.height);
|
||||
} else {
|
||||
newHeight = parseInt(height);
|
||||
}
|
||||
} else {
|
||||
// height
|
||||
newHeight = parseInt(height);
|
||||
if (maintainAspectRatio) {
|
||||
newWidth = Math.round((newHeight / img.height) * img.width);
|
||||
} else {
|
||||
newWidth = parseInt(width);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// percentage
|
||||
const scale = parseInt(percentage) / 100;
|
||||
newWidth = Math.round(img.width * scale);
|
||||
newHeight = Math.round(img.height * scale);
|
||||
}
|
||||
|
||||
// Set canvas dimensions
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
|
||||
// Draw resized image
|
||||
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
||||
|
||||
// Determine output type based on input file
|
||||
let outputType = 'image/png';
|
||||
if (file.type) {
|
||||
outputType = file.type;
|
||||
}
|
||||
|
||||
// Convert canvas to blob and create file
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const newFile = new File([blob], file.name, {
|
||||
type: outputType
|
||||
});
|
||||
setResult(newFile);
|
||||
}
|
||||
}, outputType);
|
||||
};
|
||||
|
||||
processImage(input);
|
||||
setResult(await processImage(input, optionsValues));
|
||||
};
|
||||
|
||||
const getGroups: GetGroupsType<InitialValuesType> = ({
|
||||
|
||||
162
src/pages/tools/image/generic/resize/service.ts
Normal file
162
src/pages/tools/image/generic/resize/service.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { InitialValuesType } from './types';
|
||||
|
||||
export const processImage = async (
|
||||
file: File,
|
||||
options: InitialValuesType
|
||||
): Promise<File | null> => {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
resizeMethod,
|
||||
percentage,
|
||||
dimensionType,
|
||||
maintainAspectRatio
|
||||
} = options;
|
||||
if (file.type === 'image/svg+xml') {
|
||||
try {
|
||||
// Read the SVG file
|
||||
const fileText = await file.text();
|
||||
const parser = new DOMParser();
|
||||
const svgDoc = parser.parseFromString(fileText, 'image/svg+xml');
|
||||
const svgElement = svgDoc.documentElement;
|
||||
|
||||
// Get original dimensions
|
||||
const viewBox = svgElement.getAttribute('viewBox');
|
||||
let originalWidth: string | number | null =
|
||||
svgElement.getAttribute('width');
|
||||
let originalHeight: string | number | null =
|
||||
svgElement.getAttribute('height');
|
||||
|
||||
// Parse viewBox if available and width/height are not explicitly set
|
||||
let viewBoxValues = null;
|
||||
if (viewBox) {
|
||||
viewBoxValues = viewBox.split(' ').map(Number);
|
||||
}
|
||||
|
||||
// Determine original dimensions from viewBox if not explicitly set
|
||||
if (!originalWidth && viewBoxValues && viewBoxValues.length === 4) {
|
||||
originalWidth = String(viewBoxValues[2]);
|
||||
}
|
||||
if (!originalHeight && viewBoxValues && viewBoxValues.length === 4) {
|
||||
originalHeight = String(viewBoxValues[3]);
|
||||
}
|
||||
|
||||
// Default dimensions if still not available
|
||||
originalWidth = originalWidth ? parseFloat(originalWidth) : 300;
|
||||
originalHeight = originalHeight ? parseFloat(originalHeight) : 150;
|
||||
|
||||
// Calculate new dimensions
|
||||
let newWidth = originalWidth;
|
||||
let newHeight = originalHeight;
|
||||
|
||||
if (resizeMethod === 'pixels') {
|
||||
if (dimensionType === 'width') {
|
||||
newWidth = parseInt(width);
|
||||
if (maintainAspectRatio) {
|
||||
newHeight = Math.round((newWidth / originalWidth) * originalHeight);
|
||||
} else {
|
||||
newHeight = parseInt(height);
|
||||
}
|
||||
} else {
|
||||
// height
|
||||
newHeight = parseInt(height);
|
||||
if (maintainAspectRatio) {
|
||||
newWidth = Math.round((newHeight / originalHeight) * originalWidth);
|
||||
} else {
|
||||
newWidth = parseInt(width);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// percentage
|
||||
const scale = parseInt(percentage) / 100;
|
||||
newWidth = Math.round(originalWidth * scale);
|
||||
newHeight = Math.round(originalHeight * scale);
|
||||
}
|
||||
|
||||
// Update SVG attributes
|
||||
svgElement.setAttribute('width', String(newWidth));
|
||||
svgElement.setAttribute('height', String(newHeight));
|
||||
|
||||
// If viewBox isn't already set, add it to preserve scaling
|
||||
if (!viewBox) {
|
||||
svgElement.setAttribute(
|
||||
'viewBox',
|
||||
`0 0 ${originalWidth} ${originalHeight}`
|
||||
);
|
||||
}
|
||||
|
||||
// Serialize the modified SVG document
|
||||
const serializer = new XMLSerializer();
|
||||
const svgString = serializer.serializeToString(svgDoc);
|
||||
|
||||
// Create a new file
|
||||
return new File([svgString], file.name, {
|
||||
type: 'image/svg+xml'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error processing SVG:', error);
|
||||
// Fall back to canvas method if SVG processing fails
|
||||
}
|
||||
}
|
||||
// Create canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx == null) return null;
|
||||
|
||||
// Load image
|
||||
const img = new Image();
|
||||
img.src = URL.createObjectURL(file);
|
||||
await img.decode();
|
||||
|
||||
// Calculate new dimensions
|
||||
let newWidth = img.width;
|
||||
let newHeight = img.height;
|
||||
|
||||
if (resizeMethod === 'pixels') {
|
||||
if (dimensionType === 'width') {
|
||||
newWidth = parseInt(width);
|
||||
if (maintainAspectRatio) {
|
||||
newHeight = Math.round((newWidth / img.width) * img.height);
|
||||
} else {
|
||||
newHeight = parseInt(height);
|
||||
}
|
||||
} else {
|
||||
// height
|
||||
newHeight = parseInt(height);
|
||||
if (maintainAspectRatio) {
|
||||
newWidth = Math.round((newHeight / img.height) * img.width);
|
||||
} else {
|
||||
newWidth = parseInt(width);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// percentage
|
||||
const scale = parseInt(percentage) / 100;
|
||||
newWidth = Math.round(img.width * scale);
|
||||
newHeight = Math.round(img.height * scale);
|
||||
}
|
||||
|
||||
// Set canvas dimensions
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
|
||||
// Draw resized image
|
||||
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
||||
|
||||
// Determine output type based on input file
|
||||
let outputType = 'image/png';
|
||||
if (file.type) {
|
||||
outputType = file.type;
|
||||
}
|
||||
|
||||
// Convert canvas to blob and create file
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
resolve(new File([blob], file.name, { type: outputType }));
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
}, outputType);
|
||||
});
|
||||
};
|
||||
8
src/pages/tools/image/generic/resize/types.ts
Normal file
8
src/pages/tools/image/generic/resize/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type InitialValuesType = {
|
||||
resizeMethod: 'pixels' | 'percentage';
|
||||
dimensionType: 'width' | 'height';
|
||||
width: string;
|
||||
height: string;
|
||||
percentage: string;
|
||||
maintainAspectRatio: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user