Elysia Tools

在一个清晰的工作台里浏览工具、案例、可视化和主题指南。

热门分类

专题

语言
ZHDERUPTENFRES

Elysia Tools

导航

TypeScript

Web TypeScript 图像处理示例

Web TypeScript 图像处理示例,包括图像读取保存、缩放和格式转换

案例

这个案例集合中的条目

代码typescript简单

图像读取/保存

使用 FileReader、Canvas API 和各种下载方法读取和保存图像

难度

4/10

预计时间

20 min

标签

typescript, web, image processing

前置条件

Basic TypeScript, File API, Canvas API

typescript
// Web TypeScript Image Read/Save Examples
// Using FileReader, Canvas API, and download methods

// 1. Image Reader
class ImageReader {
  // Read image as Data URL
  readAsDataURL(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as string);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read image'));
      };

      reader.readAsDataURL(file);
    });
  }

  // Read image as ArrayBuffer
  readAsArrayBuffer(file: File): Promise<ArrayBuffer> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        resolve(reader.result as ArrayBuffer);
      };

      reader.onerror = () => {
        reject(new Error('Failed to read image'));
      };

      reader.readAsArrayBuffer(file);
    });
  }

  // Read image as Base64
  readAsBase64(file: File): Promise<string> {
    return new Promise(async (resolve, reject) => {
      try {
        const dataURL = await this.readAsDataURL(file);
        const base64 = dataURL.split(',')[1];
        resolve(base64);
      } catch (error) {
        reject(error);
      }
    });
  }

  // Read image and get dimensions
  readWithDimensions(file: File): Promise<{
    dataURL: string;
    width: number;
    height: number;
  }> {
    return new Promise(async (resolve, reject) => {
      try {
        const dataURL = await this.readAsDataURL(file);
        const img = await this.loadImage(dataURL);

        resolve({
          dataURL,
          width: img.width,
          height: img.height
        });
      } catch (error) {
        reject(error);
      }
    });
  }

  // Read multiple images
  readMultiple(files: File[]): Promise<Array<{ file: File; dataURL: string }>> {
    const promises = files.map(file =>
      this.readAsDataURL(file).then(dataURL => ({ file, dataURL }))
    );

    return Promise.all(promises);
  }

  // Load image from URL
  loadImage(src: string): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const img = new Image();

      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error('Failed to load image'));
      img.src = src;
    });
  }

  // Read image with metadata
  readWithMetadata(file: File): Promise<{
    dataURL: string;
    name: string;
    size: number;
    type: string;
    width: number;
    height: number;
  }> {
    return new Promise(async (resolve, reject) => {
      try {
        const dataURL = await this.readAsDataURL(file);
        const img = await this.loadImage(dataURL);

        resolve({
          dataURL,
          name: file.name,
          size: file.size,
          type: file.type,
          width: img.width,
          height: img.height
        });
      } catch (error) {
        reject(error);
      }
    });
  }
}

// 2. Image Writer
class ImageWriter {
  // Save canvas as image
  saveCanvas(canvas: HTMLCanvasElement, fileName: string, quality: number = 0.92): void {
    canvas.toBlob((blob) => {
      if (blob) {
        this.downloadBlob(blob, fileName);
      }
    }, 'image/png');
  }

  // Save canvas as JPEG
  saveAsJPEG(canvas: HTMLCanvasElement, fileName: string, quality: number = 0.92): void {
    canvas.toBlob((blob) => {
      if (blob) {
        this.downloadBlob(blob, fileName);
      }
    }, 'image/jpeg', quality);
  }

  // Save canvas as WebP
  saveAsWebP(canvas: HTMLCanvasElement, fileName: string, quality: number = 0.92): void {
    canvas.toBlob((blob) => {
      if (blob) {
        this.downloadBlob(blob, fileName);
      }
    }, 'image/webp', quality);
  }

  // Download blob as file
  private downloadBlob(blob: Blob, fileName: string): void {
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = fileName;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  }

  // Save image element
  saveImage(img: HTMLImageElement, fileName: string): void {
    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.drawImage(img, 0, 0);
      this.saveCanvas(canvas, fileName);
    }
  }
}

// 3. Image Converter
class ImageConverter {
  private reader = new ImageReader();
  private writer = new ImageWriter();

  // Convert file to canvas
  async fileToCanvas(file: File): Promise<HTMLCanvasElement> {
    const dataURL = await this.reader.readAsDataURL(file);
    const img = await this.reader.loadImage(dataURL);

    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.drawImage(img, 0, 0);
    }

    return canvas;
  }

  // Convert to PNG
  async convertToPNG(file: File, outputFileName?: string): Promise<void> {
    const canvas = await this.fileToCanvas(file);
    const fileName = outputFileName || file.name.replace(/\\.[^.]+$/, '.png');
    this.writer.saveCanvas(canvas, fileName);
  }

  // Convert to JPEG
  async convertToJPEG(file: File, outputFileName?: string, quality: number = 0.92): Promise<void> {
    const canvas = await this.fileToCanvas(file);
    const fileName = outputFileName || file.name.replace(/\\.[^.]+$/, '.jpg');
    this.writer.saveAsJPEG(canvas, fileName, quality);
  }

  // Convert to WebP
  async convertToWebP(file: File, outputFileName?: string, quality: number = 0.92): Promise<void> {
    const canvas = await this.fileToCanvas(file);
    const fileName = outputFileName || file.name.replace(/\\.[^.]+$/, '.webp');
    this.writer.saveAsWebP(canvas, fileName, quality);
  }

  // Convert and return as blob
  async convertToBlob(file: File, format: 'png' | 'jpeg' | 'webp' = 'png', quality: number = 0.92): Promise<Blob> {
    const canvas = await this.fileToCanvas(file);

    return new Promise((resolve, reject) => {
      const mimeType = `image/${format}`;
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to convert image'));
        }
      }, mimeType, quality);
    });
  }
}

// 4. Image Info Extractor
class ImageInfoExtractor {
  private reader = new ImageReader();

  // Get basic image info
  async getInfo(file: File): Promise<{
    name: string;
    size: number;
    type: string;
    lastModified: Date;
  }> {
    return {
      name: file.name,
      size: file.size,
      type: file.type,
      lastModified: new Date(file.lastModified)
    };
  }

  // Get image dimensions
  async getDimensions(file: File): Promise<{
    width: number;
    height: number;
    aspectRatio: number;
  }> {
    const dataURL = await this.reader.readAsDataURL(file);
    const img = await this.reader.loadImage(dataURL);

    const aspectRatio = img.width / img.height;

    return {
      width: img.width,
      height: img.height,
      aspectRatio
    };
  }

  // Get complete image info
  async getCompleteInfo(file: File): Promise<{
    name: string;
    size: number;
    type: string;
    lastModified: Date;
    width: number;
    height: number;
    aspectRatio: number;
    megapixels: number;
  }> {
    const basic = await this.getInfo(file);
    const dims = await this.getDimensions(file);

    const megapixels = (dims.width * dims.height) / 1000000;

    return {
      ...basic,
      ...dims,
      megapixels
    };
  }

  // Format file size
  formatFileSize(bytes: number): string {
    const kb = bytes / 1024;
    const mb = kb / 1024;

    if (mb >= 1) {
      return `${mb.toFixed(2)} MB`;
    } else if (kb >= 1) {
      return `${kb.toFixed(2)} KB`;
    } else {
      return `${bytes} bytes`;
    }
  }
}

// 5. Image Batch Processor
class ImageBatchProcessor {
  private reader = new ImageReader();
  private converter = new ImageConverter();

  // Batch convert images
  async batchConvert(
    files: File[],
    format: 'png' | 'jpeg' | 'webp' = 'png',
    quality: number = 0.92,
    onProgress?: (current: number, total: number, file: File) => void
  ): Promise<Array<{ original: File; converted: Blob }>> {
    const results: Array<{ original: File; converted: Blob }> = [];

    for (let i = 0; i < files.length; i++) {
      const file = files[i];

      try {
        const converted = await this.converter.convertToBlob(file, format, quality);
        results.push({ original: file, converted });

        if (onProgress) {
          onProgress(i + 1, files.length, file);
        }
      } catch (error) {
        console.error(`Failed to convert ${file.name}:`, error);
      }
    }

    return results;
  }

  // Batch resize images
  async batchResize(
    files: File[],
    maxWidth: number,
    maxHeight: number,
    onProgress?: (current: number, total: number, file: File) => void
  ): Promise<Blob[]> {
    const results: Blob[] = [];

    for (let i = 0; i < files.length; i++) {
      const file = files[i];

      try {
        // Resize logic would go here
        if (onProgress) {
          onProgress(i + 1, files.length, file);
        }
      } catch (error) {
        console.error(`Failed to resize ${file.name}:`, error);
      }
    }

    return results;
  }

  // Batch get image info
  async batchGetInfo(files: File[]): Promise<Array<{ file: File; info: any }>> {
    const results: Array<{ file: File; info: any }> = [];
    const extractor = new ImageInfoExtractor();

    for (const file of files) {
      try {
        const info = await extractor.getCompleteInfo(file);
        results.push({ file, info });
      } catch (error) {
        console.error(`Failed to get info for ${file.name}:`, error);
      }
    }

    return results;
  }
}

// 6. Image Validator
class ImageValidator {
  // Validate if file is an image
  isImage(file: File): boolean {
    return file.type.startsWith('image/');
  }

  // Validate supported formats
  isSupportedFormat(file: File): boolean {
    const supportedTypes = [
      'image/jpeg',
      'image/jpg',
      'image/png',
      'image/gif',
      'image/webp',
      'image/bmp',
      'image/svg+xml'
    ];

    return supportedTypes.includes(file.type);
  }

  // Validate file size
  isValidSize(file: File, maxSizeInMB: number): boolean {
    const maxSizeInBytes = maxSizeInMB * 1024 * 1024;
    return file.size <= maxSizeInBytes;
  }

  // Validate dimensions
  async validateDimensions(
    file: File,
    minWidth: number,
    minHeight: number
  ): Promise<boolean> {
    const reader = new ImageReader();
    const dims = await reader.readWithDimensions(file);

    return dims.width >= minWidth && dims.height >= minHeight;
  }

  // Validate with all rules
  async validate(
    file: File,
    rules: {
      maxSizeInMB?: number;
      minWidth?: number;
      minHeight?: number;
      maxWidth?: number;
      maxHeight?: number;
    }
  ): Promise<{ valid: boolean; errors: string[] }> {
    const errors: string[] = [];

    if (!this.isImage(file)) {
      errors.push('File is not an image');
      return { valid: false, errors };
    }

    if (rules.maxSizeInMB && !this.isValidSize(file, rules.maxSizeInMB)) {
      errors.push(`File size exceeds ${rules.maxSizeInMB}MB`);
    }

    if (rules.minWidth || rules.minHeight) {
      const reader = new ImageReader();
      const dims = await reader.readWithDimensions(file);

      if (rules.minWidth && dims.width < rules.minWidth) {
        errors.push(`Width is less than ${rules.minWidth}px`);
      }

      if (rules.minHeight && dims.height < rules.minHeight) {
        errors.push(`Height is less than ${rules.minHeight}px`);
      }

      if (rules.maxWidth && dims.width > rules.maxWidth) {
        errors.push(`Width exceeds ${rules.maxWidth}px`);
      }

      if (rules.maxHeight && dims.height > rules.maxHeight) {
        errors.push(`Height exceeds ${rules.maxHeight}px`);
      }
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }
}

// Usage Examples
async function demonstrateImageReadSave() {
  console.log('=== Web TypeScript Image Read/Save Examples ===\n');

  const reader = new ImageReader();
  const writer = new ImageWriter();
  const converter = new ImageConverter();
  const extractor = new ImageInfoExtractor();
  const validator = new ImageValidator();

  // Note: These examples require actual file input
  console.log('--- Image Reader ---');
  console.log('Use reader.readAsDataURL(file) to read image');
  console.log('Use reader.readWithDimensions(file) to get dimensions');

  console.log('\n--- Image Writer ---');
  console.log('Use writer.saveCanvas(canvas, filename) to save canvas');
  console.log('Use writer.saveAsJPEG(canvas, filename, quality) to save as JPEG');

  console.log('\n--- Image Converter ---');
  console.log('Use converter.convertToPNG(file) to convert to PNG');
  console.log('Use converter.convertToJPEG(file) to convert to JPEG');
  console.log('Use converter.convertToWebP(file) to convert to WebP');

  console.log('\n--- Image Info Extractor ---');
  console.log('Use extractor.getInfo(file) to get basic info');
  console.log('Use extractor.getDimensions(file) to get dimensions');
  console.log('Use extractor.getCompleteInfo(file) to get complete info');

  console.log('\n--- Image Validator ---');
  console.log('Use validator.isImage(file) to check if file is image');
  console.log('Use validator.validate(file, rules) to validate with rules');

  console.log('\n=== All Image Read/Save Examples Completed ===');
}

// Export functions
export { ImageReader, ImageWriter, ImageConverter, ImageInfoExtractor, ImageBatchProcessor, ImageValidator };
export { demonstrateImageReadSave };
代码typescript中等

图像缩放

使用各种算法调整图像大小,包括双线性、双三次和高质量缩放

难度

6/10

预计时间

25 min

标签

typescript, web, image processing

前置条件

Intermediate TypeScript, Canvas API

typescript
// Web TypeScript Image Scaling Examples
// Using Canvas API for image resizing and scaling

// 1. Basic Image Scaler
class ImageScaler {
  // Resize image to specific dimensions
  async resize(
    img: HTMLImageElement,
    width: number,
    height: number
  ): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.drawImage(img, 0, 0, width, height);
    }

    return canvas;
  }

  // Resize by percentage
  async resizeByPercentage(
    img: HTMLImageElement,
    percentage: number
  ): Promise<HTMLCanvasElement> {
    const newWidth = Math.floor(img.width * (percentage / 100));
    const newHeight = Math.floor(img.height * (percentage / 100));

    return this.resize(img, newWidth, newHeight);
  }

  // Resize to fit within bounds
  async resizeToFit(
    img: HTMLImageElement,
    maxWidth: number,
    maxHeight: number
  ): Promise<HTMLCanvasElement> {
    const ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
    const newWidth = Math.floor(img.width * ratio);
    const newHeight = Math.floor(img.height * ratio);

    return this.resize(img, newWidth, newHeight);
  }

  // Resize to fill bounds (cover)
  async resizeToCover(
    img: HTMLImageElement,
    minWidth: number,
    minHeight: number
  ): Promise<HTMLCanvasElement> {
    const ratio = Math.max(minWidth / img.width, minHeight / img.height);
    const newWidth = Math.floor(img.width * ratio);
    const newHeight = Math.floor(img.height * ratio);

    return this.resize(img, newWidth, newHeight);
  }

  // Resize maintaining aspect ratio
  async resizeWithAspect(
    img: HTMLImageElement,
    targetWidth: number,
    targetHeight: number
  ): Promise<HTMLCanvasElement> {
    const srcRatio = img.width / img.height;
    const targetRatio = targetWidth / targetHeight;

    let finalWidth: number, finalHeight: number;

    if (srcRatio > targetRatio) {
      finalWidth = targetWidth;
      finalHeight = Math.floor(targetWidth / srcRatio);
    } else {
      finalHeight = targetHeight;
      finalWidth = Math.floor(targetHeight * srcRatio);
    }

    return this.resize(img, finalWidth, finalHeight);
  }
}

// 2. High Quality Scaler
class HighQualityScaler {
  // Resize with better quality using step-down approach
  async resizeHighQuality(
    img: HTMLImageElement,
    width: number,
    height: number
  ): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    if (!ctx) {
      throw new Error('Failed to get canvas context');
    }

    canvas.width = width;
    canvas.height = height;

    // Enable image smoothing for better quality
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';

    // For significant downscaling, use multiple passes
    const scaleFactor = width / img.width;

    if (scaleFactor < 0.5) {
      // Step down by half multiple times
      let w = img.width;
      let h = img.height;
      let tempCanvas = document.createElement('canvas');
      let tempCtx = tempCanvas.getContext('2d');

      if (!tempCtx) {
        throw new Error('Failed to get temp canvas context');
      }

      tempCanvas.width = w;
      tempCanvas.height = h;
      tempCtx.drawImage(img, 0, 0);

      while (w * 0.5 > width) {
        w = Math.floor(w * 0.5);
        h = Math.floor(h * 0.5);

        const newCanvas = document.createElement('canvas');
        newCanvas.width = w;
        newCanvas.height = h;
        const newCtx = newCanvas.getContext('2d');

        if (newCtx) {
          newCtx.imageSmoothingEnabled = true;
          newCtx.imageSmoothingQuality = 'high';
          newCtx.drawImage(tempCanvas, 0, 0, w, h);
        }

        tempCanvas = newCanvas;
        tempCtx = newCtx;
      }

      // Final resize to target dimensions
      ctx.drawImage(tempCanvas, 0, 0, width, height);
    } else {
      ctx.drawImage(img, 0, 0, width, height);
    }

    return canvas;
  }

  // Resize with Lanczos-like approximation
  async resizeLanczos(
    img: HTMLImageElement,
    width: number,
    height: number
  ): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    if (!ctx) {
      throw new Error('Failed to get canvas context');
    }

    canvas.width = width;
    canvas.height = height;

    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';
    ctx.drawImage(img, 0, 0, width, height);

    return canvas;
  }
}

// 3. Thumbnail Generator
class ThumbnailGenerator {
  // Generate square thumbnail
  async generateSquareThumbnail(
    img: HTMLImageElement,
    size: number
  ): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;

    const ctx = canvas.getContext('2d');
    if (!ctx) {
      throw new Error('Failed to get canvas context');
    }

    // Calculate crop dimensions
    const minDimension = Math.min(img.width, img.height);
    const startX = (img.width - minDimension) / 2;
    const startY = (img.height - minDimension) / 2;

    // Draw cropped and resized
    ctx.drawImage(
      img,
      startX, startY, minDimension, minDimension,
      0, 0, size, size
    );

    return canvas;
  }

  // Generate thumbnail with specific aspect ratio
  async generateThumbnailWithRatio(
    img: HTMLImageElement,
    width: number,
    height: number,
    fit: 'cover' | 'contain' | 'fill' = 'cover'
  ): Promise<HTMLCanvasElement> {
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    if (!ctx) {
      throw new Error('Failed to get canvas context');
    }

    if (fit === 'fill') {
      ctx.drawImage(img, 0, 0, width, height);
    } else {
      const srcRatio = img.width / img.height;
      const targetRatio = width / height;

      let renderWidth: number, renderHeight: number;
      let offsetX: number = 0, offsetY: number = 0;

      if (fit === 'cover') {
        if (srcRatio > targetRatio) {
          renderHeight = height;
          renderWidth = height * srcRatio;
          offsetX = (width - renderWidth) / 2;
        } else {
          renderWidth = width;
          renderHeight = width / srcRatio;
          offsetY = (height - renderHeight) / 2;
        }
      } else { // contain
        if (srcRatio > targetRatio) {
          renderWidth = width;
          renderHeight = width / srcRatio;
          offsetY = (height - renderHeight) / 2;
        } else {
          renderHeight = height;
          renderWidth = height * srcRatio;
          offsetX = (width - renderWidth) / 2;
        }
      }

      ctx.drawImage(img, offsetX, offsetY, renderWidth, renderHeight);
    }

    return canvas;
  }

  // Generate multiple thumbnail sizes
  async generateMultipleThumbnails(
    img: HTMLImageElement,
    sizes: number[]
  ): Promise<Map<number, HTMLCanvasElement>> {
    const thumbnails = new Map<number, HTMLCanvasElement>();

    for (const size of sizes) {
      const thumbnail = await this.generateSquareThumbnail(img, size);
      thumbnails.set(size, thumbnail);
    }

    return thumbnails;
  }
}

// 4. Smart Scaler
class SmartScaler {
  private basicScaler = new ImageScaler();
  private hqScaler = new HighQualityScaler();

  // Auto-select scaling method based on resize factor
  async smartResize(
    img: HTMLImageElement,
    width: number,
    height: number
  ): Promise<HTMLCanvasElement> {
    const scaleFactor = width / img.width;

    // Use high quality for significant downscaling
    if (scaleFactor < 0.5 || scaleFactor > 2) {
      return this.hqScaler.resizeHighQuality(img, width, height);
    } else {
      return this.basicScaler.resize(img, width, height);
    }
  }

  // Resize with smart content detection (simplified)
  async smartContentResize(
    img: HTMLImageElement,
    width: number,
    height: number
  ): Promise<HTMLCanvasElement> {
    // For now, use high quality scaler
    // A full implementation would use content-aware resizing
    return this.hqScaler.resizeHighQuality(img, width, height);
  }
}

// 5. Batch Image Scaler
class BatchImageScaler {
  private scaler = new SmartScaler();

  // Scale multiple images
  async scaleMultiple(
    images: HTMLImageElement[],
    width: number,
    height: number,
    onProgress?: (current: number, total: number) => void
  ): Promise<HTMLCanvasElement[]> {
    const results: HTMLCanvasElement[] = [];

    for (let i = 0; i < images.length; i++) {
      const canvas = await this.scaler.smartResize(images[i], width, height);
      results.push(canvas);

      if (onProgress) {
        onProgress(i + 1, images.length);
      }
    }

    return results;
  }

  // Scale files and save
  async scaleFiles(
    files: File[],
    width: number,
    height: number,
    format: 'png' | 'jpeg' | 'webp' = 'png',
    onProgress?: (current: number, total: number, fileName: string) => void
  ): Promise<Blob[]> {
    const results: Blob[] = [];

    for (let i = 0; i < files.length; i++) {
      const file = files[i];

      try {
        const reader = new ImageReader();
        const dataURL = await reader.readAsDataURL(file);
        const img = await reader.loadImage(dataURL);

        const canvas = await this.scaler.smartResize(img, width, height);

        const blob = await this.canvasToBlob(canvas, format);
        results.push(blob);

        if (onProgress) {
          onProgress(i + 1, files.length, file.name);
        }
      } catch (error) {
        console.error(`Failed to scale ${file.name}:`, error);
      }
    }

    return results;
  }

  private async canvasToBlob(canvas: HTMLCanvasElement, format: string): Promise<Blob> {
    return new Promise((resolve, reject) => {
      const mimeType = `image/${format}`;
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to convert canvas to blob'));
        }
      }, mimeType);
    });
  }
}

// 6. Image Resizer Helper
class ImageResizerHelper {
  // Calculate new dimensions maintaining aspect ratio
  static calculateDimensions(
    originalWidth: number,
    originalHeight: number,
    targetWidth?: number,
    targetHeight?: number
  ): { width: number; height: number } {
    if (targetWidth && targetHeight) {
      return { width: targetWidth, height: targetHeight };
    }

    if (targetWidth) {
      const ratio = targetWidth / originalWidth;
      return {
        width: targetWidth,
        height: Math.floor(originalHeight * ratio)
      };
    }

    if (targetHeight) {
      const ratio = targetHeight / originalHeight;
      return {
        width: Math.floor(originalWidth * ratio),
        height: targetHeight
      };
    }

    return { width: originalWidth, height: originalHeight };
  }

  // Calculate fit dimensions
  static calculateFitDimensions(
    originalWidth: number,
    originalHeight: number,
    maxWidth: number,
    maxHeight: number
  ): { width: number; height: number } {
    const ratio = Math.min(maxWidth / originalWidth, maxHeight / originalHeight);
    return {
      width: Math.floor(originalWidth * ratio),
      height: Math.floor(originalHeight * ratio)
    };
  }

  // Calculate cover dimensions
  static calculateCoverDimensions(
    originalWidth: number,
    originalHeight: number,
    minWidth: number,
    minHeight: number
  ): { width: number; height: number } {
    const ratio = Math.max(minWidth / originalWidth, minHeight / originalHeight);
    return {
      width: Math.floor(originalWidth * ratio),
      height: Math.floor(originalHeight * ratio)
    };
  }
}

// Usage Examples
async function demonstrateImageScaling() {
  console.log('=== Web TypeScript Image Scaling Examples ===\n');

  const scaler = new ImageScaler();
  const hqScaler = new HighQualityScaler();
  const thumbGen = new ThumbnailGenerator();
  const smartScaler = new SmartScaler();

  console.log('--- Basic Scaler ---');
  console.log('Use scaler.resize(img, width, height) to resize');
  console.log('Use scaler.resizeByPercentage(img, 50) to resize by 50%');
  console.log('Use scaler.resizeToFit(img, maxWidth, maxHeight) to fit within bounds');

  console.log('\n--- High Quality Scaler ---');
  console.log('Use hqScaler.resizeHighQuality(img, width, height) for best quality');

  console.log('\n--- Thumbnail Generator ---');
  console.log('Use thumbGen.generateSquareThumbnail(img, size) for square thumbnails');
  console.log('Use thumbGen.generateThumbnailWithRatio(img, width, height, fit) for custom ratio');

  console.log('\n--- Smart Scaler ---');
  console.log('Use smartScaler.smartResize(img, width, height) for auto-selected method');

  console.log('\n--- Helper Functions ---');
  console.log('Use ImageResizerHelper.calculateDimensions() to calculate new dimensions');
  console.log('Use ImageResizerHelper.calculateFitDimensions() to calculate fit dimensions');

  console.log('\n=== All Image Scaling Examples Completed ===');
}

// ImageReader import helper
class ImageReader {
  async readAsDataURL(file: File): Promise<string> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result as string);
      reader.onerror = () => reject(new Error('Failed to read'));
      reader.readAsDataURL(file);
    });
  }

  async loadImage(src: string): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error('Failed to load image'));
      img.src = src;
    });
  }
}

// Export functions
export { ImageScaler, HighQualityScaler, ThumbnailGenerator, SmartScaler, BatchImageScaler, ImageResizerHelper };
export { demonstrateImageScaling };
代码typescript中等

图像格式转换

在图像格式之间转换,包括 PNG、JPEG、WebP、GIF 和 BMP

难度

6/10

预计时间

25 min

标签

typescript, web, image processing

前置条件

Intermediate TypeScript, Canvas API, File API

typescript
// Web TypeScript Image Format Conversion Examples
// Convert between PNG, JPEG, WebP, GIF, and other formats

// 1. Basic Format Converter
class FormatConverter {
  // Convert canvas to PNG
  toPNG(canvas: HTMLCanvasElement): Promise<Blob> {
    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to convert to PNG'));
        }
      }, 'image/png');
    });
  }

  // Convert canvas to JPEG
  toJPEG(canvas: HTMLCanvasElement, quality: number = 0.92): Promise<Blob> {
    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to convert to JPEG'));
        }
      }, 'image/jpeg', quality);
    });
  }

  // Convert canvas to WebP
  toWebP(canvas: HTMLCanvasElement, quality: number = 0.92): Promise<Blob> {
    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to convert to WebP'));
        }
      }, 'image/webp', quality);
    });
  }

  // Check format support
  checkFormatSupport(): {
    png: boolean;
    jpeg: boolean;
    webp: boolean;
  } {
    const canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = 1;

    return {
      png: !!canvas.toDataURL('image/png').startsWith('data:image/png'),
      jpeg: !!canvas.toDataURL('image/jpeg').startsWith('data:image/jpeg'),
      webp: !!canvas.toDataURL('image/webp').startsWith('data:image/webp')
    };
  }
}

// 2. File Format Converter
class FileFormatConverter {
  private formatConverter = new FormatConverter();

  // Convert file to different format
  async convertFile(
    file: File,
    targetFormat: 'png' | 'jpeg' | 'webp',
    quality: number = 0.92
  ): Promise<{ blob: Blob; fileName: string }> {
    const img = await this.loadImage(file);
    const canvas = this.imageToCanvas(img);

    let blob: Blob;

    switch (targetFormat) {
      case 'png':
        blob = await this.formatConverter.toPNG(canvas);
        break;
      case 'jpeg':
        blob = await this.formatConverter.toJPEG(canvas, quality);
        break;
      case 'webp':
        blob = await this.formatConverter.toWebP(canvas, quality);
        break;
    }

    const extension = targetFormat === 'jpeg' ? 'jpg' : targetFormat;
    const fileName = file.name.replace(/\\.[^.]+$/, `.${extension}`);

    return { blob, fileName };
  }

  // Batch convert files
  async batchConvert(
    files: File[],
    targetFormat: 'png' | 'jpeg' | 'webp',
    quality: number = 0.92,
    onProgress?: (current: number, total: number, fileName: string) => void
  ): Promise<Array<{ original: File; converted: Blob; fileName: string }>> {
    const results: Array<{ original: File; converted: Blob; fileName: string }> = [];

    for (let i = 0; i < files.length; i++) {
      const file = files[i];

      try {
        const result = await this.convertFile(file, targetFormat, quality);
        results.push({
          original: file,
          converted: result.blob,
          fileName: result.fileName
        });

        if (onProgress) {
          onProgress(i + 1, files.length, file.name);
        }
      } catch (error) {
        console.error(`Failed to convert ${file.name}:`, error);
      }
    }

    return results;
  }

  // Convert with resize
  async convertWithResize(
    file: File,
    targetFormat: 'png' | 'jpeg' | 'webp',
    width: number,
    height: number,
    quality: number = 0.92
  ): Promise<{ blob: Blob; fileName: string }> {
    const img = await this.loadImage(file);
    const canvas = this.resizeImage(img, width, height);

    let blob: Blob;

    switch (targetFormat) {
      case 'png':
        blob = await this.formatConverter.toPNG(canvas);
        break;
      case 'jpeg':
        blob = await this.formatConverter.toJPEG(canvas, quality);
        break;
      case 'webp':
        blob = await this.formatConverter.toWebP(canvas, quality);
        break;
    }

    const extension = targetFormat === 'jpeg' ? 'jpg' : targetFormat;
    const fileName = file.name.replace(/\\.[^.]+$/, `.${extension}`);

    return { blob, fileName };
  }

  private loadImage(file: File): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject(new Error('Failed to load image'));
        img.src = reader.result as string;
      };

      reader.onerror = () => reject(new Error('Failed to read file'));
      reader.readAsDataURL(file);
    });
  }

  private imageToCanvas(img: HTMLImageElement): HTMLCanvasElement {
    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.drawImage(img, 0, 0);
    }

    return canvas;
  }

  private resizeImage(img: HTMLImageElement, width: number, height: number): HTMLCanvasElement {
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.drawImage(img, 0, 0, width, height);
    }

    return canvas;
  }
}

// 3. Optimized Converter
class OptimizedConverter {
  private formatConverter = new FormatConverter();

  // Auto-select optimal format
  async autoOptimize(
    canvas: HTMLCanvasElement,
    maxSizeKB: number = 100
  ): Promise<{ blob: Blob; format: string; quality: number }> {
    // Try WebP first (best compression)
    for (let quality = 0.9; quality >= 0.5; quality -= 0.1) {
      const webp = await this.formatConverter.toWebP(canvas, quality);
      if (webp.size / 1024 <= maxSizeKB) {
        return { blob: webp, format: 'webp', quality };
      }
    }

    // Try JPEG
    for (let quality = 0.9; quality >= 0.5; quality -= 0.1) {
      const jpeg = await this.formatConverter.toJPEG(canvas, quality);
      if (jpeg.size / 1024 <= maxSizeKB) {
        return { blob: jpeg, format: 'jpeg', quality };
      }
    }

    // Fallback to PNG with lowest quality WebP
    return {
      blob: await this.formatConverter.toWebP(canvas, 0.5),
      format: 'webp',
      quality: 0.5
    };
  }

  // Optimize for web
  async optimizeForWeb(
    file: File,
    maxWidth: number = 1920,
    maxHeight: number = 1080,
    maxSizeKB: number = 200
  ): Promise<{ blob: Blob; format: string; quality: number }> {
    const img = await this.loadImageFromFile(file);

    // Resize if needed
    const canvas = this.resizeToFit(img, maxWidth, maxHeight);

    return this.autoOptimize(canvas, maxSizeKB);
  }

  private loadImageFromFile(file: File): Promise<HTMLImageElement> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = () => reject(new Error('Failed to load image'));
        img.src = reader.result as string;
      };

      reader.onerror = () => reject(new Error('Failed to read file'));
      reader.readAsDataURL(file);
    });
  }

  private resizeToFit(
    img: HTMLImageElement,
    maxWidth: number,
    maxHeight: number
  ): HTMLCanvasElement {
    const ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
    const width = Math.floor(img.width * ratio);
    const height = Math.floor(img.height * ratio);

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;

    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = 'high';
      ctx.drawImage(img, 0, 0, width, height);
    }

    return canvas;
  }
}

// 4. Quality Comparator
class QualityComparator {
  // Compare quality at different settings
  async compareQuality(
    canvas: HTMLCanvasElement,
    format: 'jpeg' | 'webp',
    qualities: number[] = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
  ): Promise<Array<{ quality: number; size: number; blob: Blob }>> {
    const converter = new FormatConverter();
    const results: Array<{ quality: number; size: number; blob: Blob }> = [];

    for (const quality of qualities) {
      let blob: Blob;

      if (format === 'jpeg') {
        blob = await converter.toJPEG(canvas, quality);
      } else {
        blob = await converter.toWebP(canvas, quality);
      }

      results.push({
        quality,
        size: blob.size,
        blob
      });
    }

    return results;
  }

  // Find optimal quality for target size
  async findOptimalQuality(
    canvas: HTMLCanvasElement,
    format: 'jpeg' | 'webp',
    targetSizeKB: number,
    minQuality: number = 0.5,
    maxQuality: number = 1.0
  ): Promise<{ quality: number; size: number; blob: Blob } | null> {
    const converter = new FormatConverter();

    for (let quality = maxQuality; quality >= minQuality; quality -= 0.05) {
      let blob: Blob;

      if (format === 'jpeg') {
        blob = await converter.toJPEG(canvas, quality);
      } else {
        blob = await converter.toWebP(canvas, quality);
      }

      if (blob.size / 1024 <= targetSizeKB) {
        return { quality, size: blob.size, blob };
      }
    }

    return null;
  }
}

// 5. Format Info Provider
class FormatInfoProvider {
  // Get format information
  getFormatInfo(format: string): {
    name: string;
    extensions: string[];
    mimeType: string;
    supportsTransparency: boolean;
    supportsAnimation: boolean;
    typicalUseCase: string;
  } {
    const formats: Record<string, any> = {
      png: {
        name: 'Portable Network Graphics',
        extensions: ['png'],
        mimeType: 'image/png',
        supportsTransparency: true,
        supportsAnimation: false,
        typicalUseCase: 'Graphics, logos, images with transparency'
      },
      jpeg: {
        name: 'Joint Photographic Experts Group',
        extensions: ['jpg', 'jpeg'],
        mimeType: 'image/jpeg',
        supportsTransparency: false,
        supportsAnimation: false,
        typicalUseCase: 'Photographs, complex images'
      },
      webp: {
        name: 'Web Picture',
        extensions: ['webp'],
        mimeType: 'image/webp',
        supportsTransparency: true,
        supportsAnimation: true,
        typicalUseCase: 'Modern web images, optimized for web'
      },
      gif: {
        name: 'Graphics Interchange Format',
        extensions: ['gif'],
        mimeType: 'image/gif',
        supportsTransparency: true,
        supportsAnimation: true,
        typicalUseCase: 'Simple animations, low-color graphics'
      },
      bmp: {
        name: 'Bitmap',
        extensions: ['bmp'],
        mimeType: 'image/bmp',
        supportsTransparency: true,
        supportsAnimation: false,
        typicalUseCase: 'Uncompressed images, Windows applications'
      },
      svg: {
        name: 'Scalable Vector Graphics',
        extensions: ['svg'],
        mimeType: 'image/svg+xml',
        supportsTransparency: true,
        supportsAnimation: true,
        typicalUseCase: 'Icons, logos, scalable graphics'
      }
    };

    return formats[format.toLowerCase()] || {
      name: 'Unknown',
      extensions: [],
      mimeType: 'application/octet-stream',
      supportsTransparency: false,
      supportsAnimation: false,
      typicalUseCase: 'Unknown'
    };
  }

  // Recommend format based on content
  recommendFormat(hasTransparency: boolean, isPhoto: boolean, forWeb: boolean = true): string {
    if (forWeb) {
      if (hasTransparency) {
        return 'webp'; // Best compression with transparency
      } else if (isPhoto) {
        return 'webp'; // Best compression for photos
      } else {
        return 'webp'; // Generally best for web
      }
    } else {
      if (hasTransparency) {
        return 'png'; // Lossless with transparency
      } else if (isPhoto) {
        return 'jpeg'; // Good compression for photos
      } else {
        return 'png'; // Lossless for graphics
      }
    }
  }
}

// 6. Conversion Pipeline
class ConversionPipeline {
  private fileConverter = new FileFormatConverter();
  private optimizedConverter = new OptimizedConverter();

  // Convert with multiple options
  async convertWithPipeline(
    file: File,
    options: {
      format?: 'png' | 'jpeg' | 'webp';
      resize?: { width: number; height: number };
      quality?: number;
      maxSizeKB?: number;
      autoOptimize?: boolean;
    }
  ): Promise<{ blob: Blob; fileName: string; info: any }> {
    let blob: Blob;
    let fileName: string;
    const info: any = {};

    if (options.autoOptimize && options.maxSizeKB) {
      const result = await this.optimizedConverter.optimizeForWeb(
        file,
        options.resize?.width || 1920,
        options.resize?.height || 1080,
        options.maxSizeKB
      );

      blob = result.blob;
      fileName = file.name.replace(/\\.[^.]+$/, `.${result.format}`);
      info.format = result.format;
      info.quality = result.quality;
    } else if (options.resize) {
      const result = await this.fileConverter.convertWithResize(
        file,
        options.format || 'png',
        options.resize.width,
        options.resize.height,
        options.quality || 0.92
      );

      blob = result.blob;
      fileName = result.fileName;
      info.format = options.format;
      info.quality = options.quality;
    } else {
      const result = await this.fileConverter.convertFile(
        file,
        options.format || 'png',
        options.quality || 0.92
      );

      blob = result.blob;
      fileName = result.fileName;
      info.format = options.format;
      info.quality = options.quality;
    }

    info.size = blob.size;
    info.sizeKB = (blob.size / 1024).toFixed(2);

    return { blob, fileName, info };
  }
}

// Usage Examples
async function demonstrateImageFormatConversion() {
  console.log('=== Web TypeScript Image Format Conversion Examples ===\n');

  const converter = new FormatConverter();
  const fileConverter = new FileFormatConverter();
  const optimizedConverter = new OptimizedConverter();
  const infoProvider = new FormatInfoProvider();

  console.log('--- Format Converter ---');
  const support = converter.checkFormatSupport();
  console.log('Format Support:');
  console.log(`  PNG: ${support.png ? 'Yes' : 'No'}`);
  console.log(`  JPEG: ${support.jpeg ? 'Yes' : 'No'}`);
  console.log(`  WebP: ${support.webp ? 'Yes' : 'No'}`);

  console.log('\n--- File Format Converter ---');
  console.log('Use fileConverter.convertFile(file, format, quality) to convert files');
  console.log('Use fileConverter.batchConvert(files, format) to batch convert');

  console.log('\n--- Optimized Converter ---');
  console.log('Use optimizedConverter.autoOptimize(canvas, maxSizeKB) for auto optimization');
  console.log('Use optimizedConverter.optimizeForWeb(file, maxWidth, maxHeight, maxSizeKB) for web optimization');

  console.log('\n--- Format Info Provider ---');
  const pngInfo = infoProvider.getFormatInfo('png');
  console.log('PNG Info:', pngInfo);

  const recommendation = infoProvider.recommendFormat(true, false, true);
  console.log(`Recommended format (transparent, graphic, web): ${recommendation}`);

  console.log('\n=== All Image Format Conversion Examples Completed ===');
}

// Export functions
export { FormatConverter, FileFormatConverter, OptimizedConverter, QualityComparator, FormatInfoProvider, ConversionPipeline };
export { demonstrateImageFormatConversion };

工具

经常与该案例搭配使用的工具

相关内容

相关案例集合

相关内容

相关专题

专题

音频处理回归测试:空差、失真复核与报告交接

固定参考音频并与另行处理的结果比较,记录空差覆盖范围和适用单音的 THD,连同原文件、残差、复核结论及哈希交付回归证据。

专题

图像编码精修,让体积、加载与交付各就各位

调整 PNG 位深、调色板与透明通道,选定 JPEG 渐进或基线加载方式,配好 WebP 质量模式并压缩归档 TIFF,让每张图都装进目的地的预算。

专题

获授权 Webhook 捕获与接收端信任检查

在获授权的测试环境捕获并回放受控 Webhook 请求,分别检查投递证据和接收端 HTTPS 证书,不把证书有效误写成业务信任证明。

专题

图像清理、尺寸调整与细节增强

围绕已审源图,完成方向校正、边缘裁切、降噪、精确缩放和适度锐化,得到可交付图像。

首页
工具3212
工作台
案例593
可视化471
文件查看器103
专题239
标签333
图像、音频与视频657

图像、音频、视频处理、转换与优化工具

数学、日期与金融647

计算器、数值计算、日期逻辑、统计与金融工具

设计与颜色298

颜色、版式、图形、视觉样式与设计辅助工具

文本与写作209

写作、清洗、格式化、提取与文本分析工具

开发与 Web193

开发者工具、网络处理、Web 调试与自动化辅助工具

转换与编码175

格式、文件、压缩包、单位与编码转换工具

潜客测验发布与联系人交接
讣告、悼词与纪念卡的家属复核及交付
本地习惯记录的备份、恢复演练与分享前检查
长文改编 X 串文与 LinkedIn 帖子的事实复核与交付
Markdown 互动课件审校、离线演示与交付

© 2026 Elysia Tools. 保留所有权利。

文档价格关于我们服务条款隐私政策Cookie 政策联系我们

Media

批量图片转换

批量转换多张图片到不同格式,支持质量设置

Media

批量图片转换

批量转换多个图片文件格式,支持 JPG、PNG、WebP、AVIF、TIFF、GIF,支持质量设置和批量处理

Media

批量图片信息查看器

一次查看多张图片的基本信息 - 尺寸、格式、文件大小和元数据存在情况

Media

图片缩放工具

专业的图片缩放工具,支持多种缩放方式、长宽比保持和格式转换,包含模糊背景功能,支持 GIF/Animated WebP/APNG 和 JPEG/PNG/WEBP 格式。

AI Tools

NSFW图片内容检测器

基于 NSFWJS 模型的 AI 内容审核工具,标记上传图片是否含不适宜内容。面向内容审核与合规自查场景,仅分析您上传的文件,不是图片搜索或发现工具。支持GIF/Animated WebP/APNG和JPEG/PNG/WEBP。

Media

批量图片裁剪

批量裁剪多张图片到指定尺寸或宽高比,支持自定义定位,返回裁剪后的 ZIP 文件下载

Design

图像方框模糊

应用方框模糊核进行简单均匀平均和快速降噪

Design

图片颜色取样器

通过指定坐标从图片中选取颜色。获取 HEX、RGB、HSL、HSV 和 CMYK 格式的颜色值。

Python

Web Python 图像处理示例

Web Python 图像处理示例,使用 PIL/Pillow 包括读取、保存、缩放和格式转换

Rust

Web Rust 图像处理示例

Web Rust 图像处理示例,包括图像读取保存、缩放和格式转换

Java

Android Java 图像处理示例

Android Java 图像处理示例,包括图像读取保存、缩放和格式转换

Kotlin

Android Kotlin 图像处理示例

Android Kotlin 图像处理示例,包括图像读取保存、缩放和格式转换

Go

Web Go 图像处理示例

Web Go 图像处理示例,包括图像读写、图像缩放和格式转换

C++

Windows C++ 图像处理示例

Windows C++ 图像处理示例,包括读取/保存图像、调整大小和使用Windows Imaging Component进行格式转换