feat(new tool) image-resizer
This commit is contained in:
		
							parent
							
								
									0b1b98f93e
								
							
						
					
					
						commit
						04d3fa221c
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -101,6 +101,7 @@ declare module '@vue/runtime-core' { | ||||
|     IconMdiSearch: typeof import('~icons/mdi/search')['default'] | ||||
|     IconMdiTranslate: typeof import('~icons/mdi/translate')['default'] | ||||
|     IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] | ||||
|     ImageResizer: typeof import('./src/tools/image-resizer/image-resizer.vue')['default'] | ||||
|     InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] | ||||
|     IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] | ||||
|     Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] | ||||
|  | ||||
| @ -392,3 +392,7 @@ tools: | ||||
|   text-to-binary: | ||||
|     title: Text to ASCII binary | ||||
|     description: Convert text to its ASCII binary representation and vice-versa. | ||||
| 
 | ||||
|   image-resizer: | ||||
|     title: Image resizer | ||||
|     description: Convert the width and height of an image file, preview, and download it in a desired format (.jpg, .jpeg, .png, .bmp, .ico, .svg) | ||||
							
								
								
									
										197
									
								
								src/tools/image-resizer/image-resizer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										197
									
								
								src/tools/image-resizer/image-resizer.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,197 @@ | ||||
| <script setup lang="ts"> | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| // State variables | ||||
| const imageFile = ref<File | null>(null); | ||||
| const imageUrl = ref<string | null>(null); | ||||
| const originalImageUrl = ref<string | null>(null); // To store original image data | ||||
| const imageWidth = ref(500); // Default width | ||||
| const imageHeight = ref(350); // Default height | ||||
| const originalImageWidth = ref<number | null>(null); // Store original image width | ||||
| const originalImageHeight = ref<number | null>(null); // Store original image height | ||||
| const resizedImageUrl = ref<string | null>(null); | ||||
| 
 | ||||
| // Watch width to trigger resizing | ||||
| watch(imageWidth, () => { | ||||
|   resizeImage(); | ||||
| }); | ||||
| 
 | ||||
| // Watch height to trigger resizing | ||||
| watch(imageHeight, () => { | ||||
|   resizeImage(); | ||||
| }); | ||||
| 
 | ||||
| // Handle file upload | ||||
| async function handleFileUpload(uploadedFile: File) { | ||||
|   if (!uploadedFile) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   imageFile.value = uploadedFile; | ||||
| 
 | ||||
|   try { | ||||
|     // Read the file as a Data URL | ||||
|     const reader = new FileReader(); | ||||
|     const fileDataUrl = await new Promise<string>((resolve, reject) => { | ||||
|       reader.onload = () => resolve(reader.result as string); | ||||
|       reader.onerror = error => reject(error); | ||||
|       reader.readAsDataURL(uploadedFile); | ||||
|     }); | ||||
| 
 | ||||
|     imageUrl.value = fileDataUrl; // Preview image | ||||
|     originalImageUrl.value = fileDataUrl; // Store original image for resizing | ||||
|     resizedImageUrl.value = null; // Clear previous resized image | ||||
| 
 | ||||
|     // Create an image to get original dimensions | ||||
|     const img = new Image(); | ||||
|     img.src = fileDataUrl; | ||||
| 
 | ||||
|     await new Promise<void>((resolve) => { | ||||
|       img.onload = () => { | ||||
|         // Set original image dimensions | ||||
|         originalImageWidth.value = img.naturalWidth; | ||||
|         originalImageHeight.value = img.naturalHeight; | ||||
| 
 | ||||
|         // Automatically resize if width and height are set | ||||
|         if (imageWidth.value > 0 && imageHeight.value > 0) { | ||||
|           resizeImage(); | ||||
|         } | ||||
| 
 | ||||
|         resolve(); | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
|   catch (error) { | ||||
|     console.error('Error reading file:', error); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Function to resize the image | ||||
| async function resizeImage() { | ||||
|   if (!originalImageUrl.value) { | ||||
|     return; // Ensure there's an original image to work with | ||||
|   } | ||||
| 
 | ||||
|   const img = new Image(); | ||||
|   img.src = originalImageUrl.value; // Use the original image for resizing | ||||
| 
 | ||||
|   img.onload = () => { | ||||
|     const canvas = document.createElement('canvas'); | ||||
|     canvas.width = imageWidth.value; | ||||
|     canvas.height = imageHeight.value; | ||||
| 
 | ||||
|     const ctx = canvas.getContext('2d'); | ||||
|     ctx?.drawImage(img, 0, 0, imageWidth.value, imageHeight.value); | ||||
|     resizedImageUrl.value = canvas.toDataURL('image/png'); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| // Function to download resized image | ||||
| function downloadImage(format: string) { | ||||
|   if (!resizedImageUrl.value || !imageFile.value) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const originalFilename = imageFile.value.name.replace(/\.[^/.]+$/, ''); // Remove file extension | ||||
|   const newFilename = `${originalFilename}-${imageWidth.value}x${imageHeight.value}.${format}`; | ||||
|   const link = document.createElement('a'); | ||||
|   link.href = resizedImageUrl.value; | ||||
|   link.download = newFilename; | ||||
|   link.click(); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <n-card> | ||||
|     <div> | ||||
|       <!-- File input --> | ||||
|       <c-file-upload mb-2 accept=".jpg,.jpeg,.png,.bmp,.ico,.svg" title="Drag and drop a .jpg, .jpeg, .png, .bmp, .ico, .svg file here" @file-upload="handleFileUpload" /> | ||||
| 
 | ||||
|       <!-- Original image dimensions --> | ||||
|       <div v-if="originalImageWidth && originalImageHeight"> | ||||
|         <p>Original Image Dimensions: {{ originalImageWidth }}x{{ originalImageHeight }}px</p> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- Width and height inputs --> | ||||
|       <div class="input-group"> | ||||
|         <label for="widthInput">Width (px):</label> | ||||
|         <n-input-number id="widthInput" v-model:value="imageWidth" placeholder="Width (px)" mb-3 /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="input-group"> | ||||
|         <label for="heightInput">Height (px):</label> | ||||
|         <n-input-number id="heightInput" v-model:value="imageHeight" placeholder="Height (px)" mb-1 /> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- Image preview --> | ||||
|     <div v-if="resizedImageUrl" class="image-container" style="text-align: center; margin-top: 20px;"> | ||||
|       <div class="image-wrapper"> | ||||
|         <img :src="resizedImageUrl" :alt="`Resized Preview (${imageWidth}px x ${imageHeight}px)`" :style="{ width: `${imageWidth}px`, height: `${imageHeight}px` }"> | ||||
|       </div> | ||||
|       <p>Preview: {{ imageWidth }}x{{ imageHeight }}px</p> | ||||
| 
 | ||||
|       <!-- Download options --> | ||||
|       <h3>Download Options:</h3> | ||||
|       <div class="download-grid"> | ||||
|         <n-button @click.prevent="downloadImage('jpg')"> | ||||
|           Download JPG | ||||
|         </n-button> | ||||
|         <n-button @click.prevent="downloadImage('png')"> | ||||
|           Download PNG | ||||
|         </n-button> | ||||
|         <n-button @click.prevent="downloadImage('bmp')"> | ||||
|           Download BMP | ||||
|         </n-button> | ||||
|         <n-button @click.prevent="downloadImage('svg')"> | ||||
|           Download SVG | ||||
|         </n-button> | ||||
|         <n-button @click.prevent="downloadImage('ico')"> | ||||
|           Download ICO | ||||
|         </n-button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </n-card> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
| .input-group { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   justify-content: center; | ||||
|   align-items: left; | ||||
| } | ||||
| 
 | ||||
| .input-group label { | ||||
|   margin-right: 10px; | ||||
|   width: 100px; | ||||
| } | ||||
| 
 | ||||
| .image-container { | ||||
|   max-width: 100%; | ||||
|   overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .image-wrapper { | ||||
|   max-width: 100%; | ||||
|   max-height: 500px; | ||||
|   overflow: auto; | ||||
| } | ||||
| 
 | ||||
| .download-grid { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 10px; | ||||
|   margin-top: 10px; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   color: #007bff; | ||||
|   text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| a:hover { | ||||
|   text-decoration: underline; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										12
									
								
								src/tools/image-resizer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/image-resizer/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { IconResize } from '@tabler/icons-vue'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'Image resizer', | ||||
|   path: '/image-resizer', | ||||
|   description: '', | ||||
|   keywords: ['image', 'resizer', 'favicon', 'jpg', 'jpeg', 'png', 'bmp', 'ico', 'svg'], | ||||
|   component: () => import('./image-resizer.vue'), | ||||
|   icon: IconResize, | ||||
|   createdAt: new Date('2024-10-22'), | ||||
| }); | ||||
| @ -1,6 +1,7 @@ | ||||
| import { tool as base64FileConverter } from './base64-file-converter'; | ||||
| import { tool as base64StringConverter } from './base64-string-converter'; | ||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | ||||
| import { tool as imageResizer } from './image-resizer'; | ||||
| import { tool as emailNormalizer } from './email-normalizer'; | ||||
| 
 | ||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||
| @ -141,7 +142,13 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|   }, | ||||
|   { | ||||
|     name: 'Images and videos', | ||||
|     components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], | ||||
|     components: [ | ||||
|       qrCodeGenerator, | ||||
|       wifiQrCodeGenerator, | ||||
|       svgPlaceholderGenerator, | ||||
|       cameraRecorder, | ||||
|       imageResizer, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     name: 'Development', | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user