diff --git a/src/pages/tools/image/generic/convert-to-jpg/index.tsx b/src/pages/tools/image/generic/convert-to-jpg/index.tsx new file mode 100644 index 0000000..c7e01b6 --- /dev/null +++ b/src/pages/tools/image/generic/convert-to-jpg/index.tsx @@ -0,0 +1,164 @@ +import { Box, Slider, Typography } from '@mui/material'; +import ToolImageInput from 'components/input/ToolImageInput'; +import ColorSelector from 'components/options/ColorSelector'; +import ToolFileResult from 'components/result/ToolFileResult'; +import Color from 'color'; +import React, { useState } from 'react'; +import * as Yup from 'yup'; +import ToolContent from '@components/ToolContent'; +import { ToolComponentProps } from '@tools/defineTool'; + +const initialValues = { + quality: 85, + backgroundColor: '#ffffff' +}; + +const validationSchema = Yup.object({ + quality: Yup.number().min(1).max(100).required('Quality is required'), + backgroundColor: Yup.string().required('Background color is required') +}); + +export default function ConvertToJpg({ title }: ToolComponentProps) { + const [input, setInput] = useState(null); + const [result, setResult] = useState(null); + + const compute = async ( + optionsValues: typeof initialValues, + input: any + ): Promise => { + if (!input) return; + + const processImage = async ( + file: File, + quality: number, + backgroundColor: string + ) => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (ctx == null) return; + + const img = new Image(); + img.src = URL.createObjectURL(file); + + try { + await img.decode(); + + canvas.width = img.width; + canvas.height = img.height; + + // Fill background with selected color (important for transparency) + let bgColor: [number, number, number]; + try { + //@ts-ignore + bgColor = Color(backgroundColor).rgb().array(); + } catch (err) { + bgColor = [255, 255, 255]; // Default to white + } + + ctx.fillStyle = `rgb(${bgColor[0]}, ${bgColor[1]}, ${bgColor[2]})`; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Draw the image on top + ctx.drawImage(img, 0, 0); + + // Convert to JPG with specified quality + canvas.toBlob( + (blob) => { + if (blob) { + const fileName = file.name.replace(/\.[^/.]+$/, '') + '.jpg'; + const newFile = new File([blob], fileName, { + type: 'image/jpeg' + }); + setResult(newFile); + } + }, + 'image/jpeg', + quality / 100 + ); + } catch (error) { + console.error('Error processing image:', error); + } finally { + URL.revokeObjectURL(img.src); + } + }; + + processImage(input, optionsValues.quality, optionsValues.backgroundColor); + }; + + return ( + + } + resultComponent={ + + } + initialValues={initialValues} + validationSchema={validationSchema} + getGroups={({ values, updateField }) => [ + { + title: 'JPG Quality Settings', + component: ( + + + + JPG Quality: {values.quality}% + + + updateField( + 'quality', + Array.isArray(value) ? value[0] : value + ) + } + min={1} + max={100} + step={1} + valueLabelDisplay="auto" + valueLabelFormat={(value) => `${value}%`} + sx={{ mt: 1 }} + /> + + Higher values = better quality, larger file size + + + + updateField('backgroundColor', val)} + description={'Background color (for transparent images)'} + inputProps={{ 'data-testid': 'background-color-input' }} + /> + + ) + } + ]} + compute={compute} + setInput={setInput} + /> + ); +} diff --git a/src/pages/tools/image/generic/convert-to-jpg/meta.ts b/src/pages/tools/image/generic/convert-to-jpg/meta.ts new file mode 100644 index 0000000..7f4c548 --- /dev/null +++ b/src/pages/tools/image/generic/convert-to-jpg/meta.ts @@ -0,0 +1,27 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; + +export const tool = defineTool('image-generic', { + name: 'Convert Images to JPG', + path: 'convert-to-jpg', + icon: 'ph:file-jpg-thin', + description: + 'Convert various image formats (PNG, GIF, TIF, PSD, SVG, WEBP, HEIC, RAW) to JPG with customizable quality and background color settings.', + shortDescription: 'Convert images to JPG with quality control', + keywords: [ + 'convert', + 'jpg', + 'jpeg', + 'png', + 'gif', + 'tiff', + 'webp', + 'heic', + 'raw', + 'psd', + 'svg', + 'quality', + 'compression' + ], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/tools/image/generic/index.ts b/src/pages/tools/image/generic/index.ts index 07e808c..c3710b9 100644 --- a/src/pages/tools/image/generic/index.ts +++ b/src/pages/tools/image/generic/index.ts @@ -8,6 +8,7 @@ import { tool as createTransparent } from './create-transparent/meta'; import { tool as imageToText } from './image-to-text/meta'; import { tool as qrCodeGenerator } from './qr-code/meta'; import { tool as rotateImage } from './rotate/meta'; +import { tool as convertToJpg } from './convert-to-jpg/meta'; export const imageGenericTools = [ resizeImage, compressImage, @@ -18,5 +19,6 @@ export const imageGenericTools = [ createTransparent, imageToText, qrCodeGenerator, - rotateImage + rotateImage, + convertToJpg ];