<!-- Generated by SpecStory -->

# 纹理优化与增量渲染方案 (2025-05-27 14:15:52)

_**User**_

这个纹理计算方式导致在 iphone 上不清晰，因为性能原因限制了纹理分辨率。

放弃现在材质的实现。使用texture tiling。效仿CATiledLayer 的实现

---

_**Assistant**_

我理解您的问题。当前的纹理实现在iPhone上由于性能限制导致分辨率不够，图像不清晰。我将重新实现一个基于texture tiling的方案，类似于CATiledLayer的实现方式。

让我先搜索一下当前的纹理相关代码，然后重新设计：

---

<details>
            <summary>Searched codebase "texture tiling CATiledLayer implementation" • **20** results</summary>
        
| File | Lines |
|------|-------|
| `src/components/ui/WebGLImageViewer.tsx` | L911-973 |
| `src/components/ui/WebGLImageViewer.tsx` | L1214-1280 |
| `src/components/ui/WebGLImageViewer.tsx` | L68-136 |
| `src/components/ui/WebGLImageViewer.tsx` | L788-835 |
| `src/components/ui/WebGLImageViewer.tsx` | L973-1021 |
| `src/components/ui/WebGLImageViewer.tsx` | L589-648 |
| `src/components/ui/WebGLImageViewer.tsx` | L136-183 |
| `src/components/ui/WebGLImageViewer.tsx` | L451-513 |
| `src/components/ui/WebGLImageViewer.tsx` | L735-756 |
| `src/components/ui/WebGLImageViewer.tsx` | L183-219 |
| `src/components/ui/WebGLImageViewer.tsx` | L663-685 |
| `src/components/ui/WebGLImageViewer.tsx` | L1079-1117 |
| `src/components/ui/WebGLImageViewer.tsx` | L513-574 |
| `src/components/ui/WebGLImageViewer.tsx` | L1286-1310 |
| `src/components/ui/WebGLImageViewer.tsx` | L648-663 |
| `src/components/ui/WebGLImageViewer.tsx` | L685-735 |
| `src/components/ui/WebGLImageViewer.tsx` | L423-451 |
| `src/components/ui/WebGLImageViewer.tsx` | L885-911 |
| `src/components/ui/WebGLImageViewer.tsx` | L219-272 |
| `src/components/ui/WebGLImageViewer.tsx` | L416-423 |

</details>

---

现在我将重新实现一个基于texture tiling的WebGL图像查看器，类似于CATiledLayer的实现方式。这种方法将图像分割成多个小的瓦片(tiles)，每个瓦片都有自己的纹理，可以根据需要动态加载和卸载，从而避免单个大纹理的限制。

---

```diff
  }
  
- // WebGL Image Viewer implementation class
+ // Tile structure for texture tiling
+ interface Tile {
+   x: number // Tile grid position
+   y: number // Tile grid position
+   level: number // Zoom level (0 = original size, 1 = 2x, -1 = 0.5x, etc.)
+   texture: WebGLTexture | null
+   canvas: HTMLCanvasElement | null
+   loaded: boolean
+   loading: boolean
+   lastUsed: number
+   priority: number
+ }
+ 
+ interface TileCoordinate {
+   x: number
+   y: number
+   level: number
+ }
+ 
+ // WebGL Image Viewer implementation class with texture tiling
  class WebGLImageViewerEngine {
    private canvas: HTMLCanvasElement
    private gl: WebGLRenderingContext
    private program!: WebGLProgram
-   private texture: WebGLTexture | null = null
    private imageLoaded = false
    private originalImageSrc = ''
```

```diff
    private renderThrottleDelay = 16 // ~60fps
  
-   // Texture optimization state
+   // Tiling system state
    private originalImage: HTMLImageElement | null = null
-   private textureDebounceId: ReturnType<typeof setTimeout> | null = null
-   private textureDebounceDelay = 300 // ms
-   private currentTextureScale = 1
-   private lastOptimalTextureSize = { width: 0, height: 0 }
-   private maxTextureSize = 0 // WebGL maximum texture size
+   private tiles: Map<string, Tile> = new Map()
+   private tileSize = 512 // Fixed tile size for optimal performance
+   private maxCachedTiles = 100 // Maximum number of tiles to keep in memory
+   private tileLoadQueue: TileCoordinate[] = []
+   private isLoadingTiles = false
+   private maxZoomLevel = 4 // Maximum zoom level (16x zoom)
+   private minZoomLevel = -4 // Minimum zoom level (1/16x zoom)
  
    // Configuration
```

```diff
    private boundResizeCanvas: () => void
  
-   // Vertex and fragment shaders
+   // Vertex and fragment shaders for tiled rendering
    private vertexShaderSource = `
      attribute vec2 a_position;
      attribute vec2 a_texCoord;
      
      uniform mat3 u_matrix;
+     uniform vec2 u_tileOffset;
+     uniform vec2 u_tileScale;
      
      varying vec2 v_texCoord;
      
      void main() {
-       vec3 position = u_matrix * vec3(a_position, 1.0);
+       vec3 position = u_matrix * vec3(a_position * u_tileScale + u_tileOffset, 1.0);
        gl_Position = vec4(position.xy, 0, 1);
        v_texCoord = a_texCoord;
```

```diff
      
      uniform sampler2D u_image;
+     uniform float u_alpha;
      varying vec2 v_texCoord;
      
      void main() {
-       gl_FragColor = texture2D(u_image, v_texCoord);
+       vec4 color = texture2D(u_image, v_texCoord);
+       gl_FragColor = vec4(color.rgb, color.a * u_alpha);
      }
    `
```

```diff
        antialias: true,
        powerPreference: 'high-performance',
-       failIfMajorPerformanceCaveat: false, // 允许软件渲染作为后备
+       failIfMajorPerformanceCaveat: false,
      })
      if (!gl) {
```

```diff
      this.gl = gl
  
-     // 获取 WebGL 最大纹理尺寸
-     this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE)
- 
-     // 在移动设备上记录一些有用的调试信息
-     if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
-       console.info('WebGL Image Viewer - Mobile device detected')
-       console.info('Max texture size:', this.maxTextureSize)
-       console.info('Device pixel ratio:', window.devicePixelRatio || 1)
-       console.info(
-         'Screen size:',
-         window.screen.width,
-         'x',
-         window.screen.height,
-       )
-       console.info('WebGL renderer:', gl.getParameter(gl.RENDERER))
-       console.info('WebGL vendor:', gl.getParameter(gl.VENDOR))
-     }
- 
-     // 初始缩放将在图片加载时正确设置，这里先保持默认值
-     // this.scale = config.initialScale
+     // Detect mobile devices and adjust tile cache size
+     const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
+     if (isMobile) {
+       this.maxCachedTiles = 50 // Reduce cache size on mobile
+       this.tileSize = 256 // Smaller tiles on mobile for better memory management
+     }
  
      // Bind event handlers for proper cleanup
```

```diff
      const devicePixelRatio = window.devicePixelRatio || 1
  
-     // 使用设备像素比来提高清晰度，特别是在高DPI屏幕上
      this.canvasWidth = rect.width
      this.canvasHeight = rect.height
  
-     // 设置实际的canvas像素尺寸，考虑设备像素比
      const actualWidth = Math.round(rect.width * devicePixelRatio)
      const actualHeight = Math.round(rect.height * devicePixelRatio)
```

```diff
  
      if (this.imageLoaded) {
-       // 窗口大小改变时，需要重新约束缩放倍数和位置
        this.constrainScaleAndPosition()
+       this.updateVisibleTiles()
        this.render()
-       // canvas尺寸变化时也需要检查纹理更新
-       this.debouncedTextureUpdate()
-       // 通知缩放变化
        this.notifyZoomChange()
      }
```

```diff
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
  
-     // Create geometry (quad that will be transformed to image size)
+     // Create geometry (quad for each tile)
      const positions = new Float32Array([
        -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1,
```

```diff
          this.imageWidth = image.width
          this.imageHeight = image.height
+         this.originalImage = image
  
-         // 先设置正确的缩放值，再创建纹理
+         // Calculate zoom levels based on image size
+         this.calculateZoomLevels()
+ 
          if (this.config.centerOnInit) {
            this.fitImageToScreen()
          } else {
-           // 即使不居中，也需要将相对缩放转换为绝对缩放
            const fitToScreenScale = this.getFitToScreenScale()
            this.scale = fitToScreenScale * this.config.initialScale
          }
  
-         this.createTexture(image)
          this.imageLoaded = true
+         this.updateVisibleTiles()
          this.render()
-         this.notifyZoomChange() // 通知初始缩放值
+         this.notifyZoomChange()
          resolve()
        }
```

```diff
    }
  
-   private createTexture(image: HTMLImageElement) {
-     this.originalImage = image
-     this.createOptimizedTexture()
-   }
- 
-   private createOptimizedTexture() {
+   private calculateZoomLevels() {
      if (!this.originalImage) return
  
-     const { gl } = this
- 
-     // 计算当前最优纹理尺寸
-     const optimalSize = this.calculateOptimalTextureSize()
-     this.lastOptimalTextureSize = optimalSize
- 
-     try {
-       // 创建离屏canvas来调整图片尺寸
-       const offscreenCanvas = document.createElement('canvas')
-       const offscreenCtx = offscreenCanvas.getContext('2d')!
- 
-       offscreenCanvas.width = optimalSize.width
-       offscreenCanvas.height = optimalSize.height
- 
-       // 设置高质量的图像渲染
-       offscreenCtx.imageSmoothingEnabled = true
-       offscreenCtx.imageSmoothingQuality = 'high'
- 
-       // 对于小图片或放大的情况，使用更精细的渲染策略
-       const isSmallImage =
-         this.originalImage.width < 512 || this.originalImage.height < 512
-       const isUpscaling =
-         optimalSize.width > this.originalImage.width ||
-         optimalSize.height > this.originalImage.height
- 
-       if (isSmallImage || isUpscaling) {
-         // 对于小图片或需要放大的情况，使用最高质量的渲染
-         offscreenCtx.imageSmoothingEnabled = false // 禁用平滑以保持锐利
- 
-         // 如果是放大，先绘制到中间尺寸，再放大到目标尺寸（阶梯式放大）
-         if (
-           isUpscaling &&
-           (optimalSize.width > this.originalImage.width * 2 ||
-             optimalSize.height > this.originalImage.height * 2)
-         ) {
-           const intermediateCanvas = document.createElement('canvas')
-           const intermediateCtx = intermediateCanvas.getContext('2d')!
- 
-           // 计算中间尺寸（2倍原图大小）
-           const intermediateWidth = this.originalImage.width * 2
-           const intermediateHeight = this.originalImage.height * 2
- 
-           intermediateCanvas.width = intermediateWidth
-           intermediateCanvas.height = intermediateHeight
- 
-           intermediateCtx.imageSmoothingEnabled = false
-           intermediateCtx.drawImage(
-             this.originalImage,
-             0,
-             0,
-             this.originalImage.width,
-             this.originalImage.height,
-             0,
-             0,
-             intermediateWidth,
-             intermediateHeight,
-           )
- 
-           // 然后从中间尺寸绘制到最终尺寸
-           offscreenCtx.imageSmoothingEnabled = true
-           offscreenCtx.imageSmoothingQuality = 'high'
-           offscreenCtx.drawImage(
-             intermediateCanvas,
-             0,
-             0,
-             intermediateWidth,
-             intermediateHeight,
-             0,
-             0,
-             optimalSize.width,
-             optimalSize.height,
-           )
-         } else {
-           // 直接绘制，保持像素完美
-           offscreenCtx.drawImage(
-             this.originalImage,
-             0,
-             0,
-             this.originalImage.width,
-             this.originalImage.height,
-             0,
-             0,
-             optimalSize.width,
-             optimalSize.height,
-           )
-         }
-       } else {
-         // 对于大图片的缩小，使用标准的高质量平滑
-         offscreenCtx.drawImage(
-           this.originalImage,
-           0,
-           0,
-           this.originalImage.width,
-           this.originalImage.height,
-           0,
-           0,
-           optimalSize.width,
-           optimalSize.height,
-         )
-       }
- 
-       // 删除旧纹理
-       if (this.texture) {
-         gl.deleteTexture(this.texture)
-         this.texture = null
-       }
- 
-       // 创建新纹理
-       this.texture = gl.createTexture()
-       if (!this.texture) {
-         console.error('Failed to create WebGL texture')
-         return
-       }
- 
-       gl.bindTexture(gl.TEXTURE_2D, this.texture)
- 
-       // 设置纹理参数，防止边框效果
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
- 
-       // 根据图片类型和缩放情况选择最佳的过滤方式
-       const isSmallImageForFilter =
-         this.originalImage.width < 512 || this.originalImage.height < 512
-       const currentRelativeScale = this.scale / this.getFitToScreenScale()
- 
-       if (isSmallImageForFilter && currentRelativeScale > 1) {
-         // 对于放大的小图片，使用最近邻插值保持锐利
-         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
-         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
-       } else {
-         // 其他情况使用线性插值获得平滑效果
-         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
-         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
-       }
- 
-       // 上传纹理数据
-       gl.texImage2D(
-         gl.TEXTURE_2D,
-         0,
-         gl.RGBA,
-         gl.RGBA,
-         gl.UNSIGNED_BYTE,
-         offscreenCanvas,
-       )
- 
-       // 检查 WebGL 错误
-       const error = gl.getError()
-       if (error !== gl.NO_ERROR) {
-         console.error('WebGL texture creation error:', error)
-         // 如果纹理创建失败，尝试使用更小的尺寸
-         if (optimalSize.width > 1024 || optimalSize.height > 1024) {
-           this.createFallbackTexture()
-           return
-         }
-       }
- 
-       this.currentTextureScale = this.scale
-     } catch (error) {
-       console.error('Error creating optimized texture:', error)
-       this.createFallbackTexture()
-     }
-   }
- 
-   private createFallbackTexture() {
-     if (!this.originalImage) return
- 
-     const { gl } = this
- 
-     try {
-       // 使用更保守的纹理尺寸作为后备方案
-       const fallbackSize = this.calculateFallbackTextureSize()
-       this.lastOptimalTextureSize = fallbackSize
- 
-       const offscreenCanvas = document.createElement('canvas')
-       const offscreenCtx = offscreenCanvas.getContext('2d')!
- 
-       offscreenCanvas.width = fallbackSize.width
-       offscreenCanvas.height = fallbackSize.height
- 
-       // 对于fallback纹理也应用相同的高质量渲染策略
-       const isSmallImageFallback =
-         this.originalImage.width < 512 || this.originalImage.height < 512
-       const isUpscalingFallback =
-         fallbackSize.width > this.originalImage.width ||
-         fallbackSize.height > this.originalImage.height
- 
-       if (isSmallImageFallback || isUpscalingFallback) {
-         offscreenCtx.imageSmoothingEnabled = false
-       } else {
-         offscreenCtx.imageSmoothingEnabled = true
-         offscreenCtx.imageSmoothingQuality = 'high'
-       }
- 
-       offscreenCtx.drawImage(
-         this.originalImage,
-         0,
-         0,
-         this.originalImage.width,
-         this.originalImage.height,
-         0,
-         0,
-         fallbackSize.width,
-         fallbackSize.height,
-       )
- 
-       if (this.texture) {
-         gl.deleteTexture(this.texture)
-         this.texture = null
-       }
- 
-       this.texture = gl.createTexture()
-       if (!this.texture) {
-         console.error('Failed to create fallback WebGL texture')
-         return
-       }
- 
-       gl.bindTexture(gl.TEXTURE_2D, this.texture)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
- 
-       gl.texImage2D(
-         gl.TEXTURE_2D,
-         0,
-         gl.RGBA,
-         gl.RGBA,
-         gl.UNSIGNED_BYTE,
-         offscreenCanvas,
-       )
- 
-       this.currentTextureScale = this.scale
-     } catch (error) {
-       console.error('Error creating fallback texture:', error)
-     }
-   }
- 
-   private calculateFallbackTextureSize(): { width: number; height: number } {
-     if (!this.originalImage) return { width: 0, height: 0 }
- 
-     const aspectRatio = this.originalImage.width / this.originalImage.height
-     const devicePixelRatio = window.devicePixelRatio || 1
- 
-     // 使用相对保守但仍能保证质量的尺寸
-     // 在高DPI设备上允许稍大的后备纹理
-     const maxSize = devicePixelRatio >= 2 ? 1536 : 1024
- 
-     let width, height
-     if (aspectRatio >= 1) {
-       width = Math.min(maxSize, this.originalImage.width)
-       height = Math.min(width / aspectRatio, this.originalImage.height)
-     } else {
-       height = Math.min(maxSize, this.originalImage.height)
-       width = Math.min(height * aspectRatio, this.originalImage.width)
-     }
- 
-     return { width: Math.round(width), height: Math.round(height) }
-   }
- 
-   private calculateOptimalTextureSize(): { width: number; height: number } {
-     if (!this.originalImage) return { width: 0, height: 0 }
- 
-     // 计算原图的宽高比
-     const aspectRatio = this.originalImage.width / this.originalImage.height
- 
-     // 计算当前显示在屏幕上的图片尺寸
-     const displayWidth = this.imageWidth * this.scale
-     const displayHeight = this.imageHeight * this.scale
- 
-     // 计算设备像素比，确保在高DPI屏幕上也有足够的清晰度
-     const devicePixelRatio = window.devicePixelRatio || 1
- 
-     // 检测移动设备
-     const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
- 
-     // 基于显示尺寸和设备像素比计算目标纹理尺寸
-     let targetWidth = displayWidth * devicePixelRatio
-     let targetHeight = displayHeight * devicePixelRatio
- 
-     // 应用超采样来提高质量，减少锯齿
-     // 在高DPI屏幕上或放大时使用更高的超采样倍率
-     const fitToScreenScale = this.getFitToScreenScale()
-     const relativeScale = this.scale / fitToScreenScale
- 
-     let supersamplingFactor = 1
-     if (relativeScale >= 1) {
-       // 正常大小或放大时，使用较高的超采样
-       // 在高DPI移动设备上使用更积极的超采样
-       const maxSupersampling = devicePixelRatio >= 2 && isMobile ? 2.5 : 2
-       supersamplingFactor = Math.min(maxSupersampling, 1 + relativeScale * 0.6)
-     } else {
-       // 缩小时，仍然保持一定的超采样以减少锯齿
-       // 在高DPI设备上保持更高的基础质量
-       const baseSupersampling = devicePixelRatio >= 2 ? 1.5 : 1.2
-       supersamplingFactor = Math.max(
-         baseSupersampling,
-         Math.min(2, 2 - relativeScale),
-       )
-     }
- 
-     targetWidth *= supersamplingFactor
-     targetHeight *= supersamplingFactor
- 
-     // 应用智能的纹理大小限制
-     const isSafari =
-       /Safari/i.test(navigator.userAgent) &&
-       !/Chrome/i.test(navigator.userAgent)
- 
-     // 为移动设备设置智能的最大纹理尺寸
-     let effectiveMaxTextureSize = this.maxTextureSize
-     if (isMobile) {
-       if (isSafari) {
-         // iPhone/iPad Safari: 使用更智能的策略
-         // 对于高分辨率设备，允许更大的纹理
-         const isHighDPI = devicePixelRatio >= 2
-         const screenSize = Math.max(window.screen.width, window.screen.height)
- 
-         if (isHighDPI && screenSize >= 1024) {
-           // iPhone Pro/iPad Pro 等高端设备，允许更大纹理
-           effectiveMaxTextureSize = Math.min(4096, this.maxTextureSize)
-         } else if (screenSize >= 768) {
-           // 标准 iPhone/iPad，使用中等纹理尺寸
-           effectiveMaxTextureSize = Math.min(3072, this.maxTextureSize)
-         } else {
-           // 较小屏幕设备，使用保守尺寸
-           effectiveMaxTextureSize = Math.min(2048, this.maxTextureSize)
-         }
-       } else {
-         // Android 设备通常有更好的 WebGL 支持
-         effectiveMaxTextureSize = Math.min(4096, this.maxTextureSize)
-       }
-     }
- 
-     // 确保纹理尺寸不会过小，但也要考虑性能
-     // 对于小图片，使用更智能的最小尺寸策略
-     const isSmallOriginalImage =
-       this.originalImage.width < 512 || this.originalImage.height < 512
- 
-     let baseMinDimension
-     if (isSmallOriginalImage) {
-       // 对于小图片，允许更大的纹理尺寸以保持清晰度
-       baseMinDimension = devicePixelRatio >= 2 ? 2048 : 1536
-     } else {
-       // 对于大图片，使用标准的最小尺寸
-       baseMinDimension = devicePixelRatio >= 2 ? 1024 : 512
-     }
- 
-     const minDimension = Math.min(
-       Math.max(baseMinDimension, devicePixelRatio >= 3 ? 1536 : 1024),
-       Math.max(
-         baseMinDimension,
-         // 对于小图片，允许纹理尺寸超过原图尺寸
-         isSmallOriginalImage
-           ? baseMinDimension
-           : Math.min(this.originalImage.width, this.originalImage.height),
-       ),
-     )
- 
-     // 根据宽高比确定最小尺寸
-     let minWidth, minHeight
-     if (aspectRatio >= 1) {
-       // 宽图或正方形
-       minWidth = minDimension
-       minHeight = minDimension / aspectRatio
-     } else {
-       // 高图
-       minWidth = minDimension * aspectRatio
-       minHeight = minDimension
-     }
- 
-     // 应用最小尺寸约束，但保持宽高比
-     if (targetWidth < minWidth || targetHeight < minHeight) {
-       const scaleToMin = Math.max(
-         minWidth / targetWidth,
-         minHeight / targetHeight,
-       )
-       targetWidth *= scaleToMin
-       targetHeight *= scaleToMin
-     }
- 
-     // 纹理尺寸永远不应该超过原图尺寸
-     targetWidth = Math.min(targetWidth, this.originalImage.width)
-     targetHeight = Math.min(targetHeight, this.originalImage.height)
- 
-     // 应用 WebGL 最大纹理尺寸限制
-     targetWidth = Math.min(targetWidth, effectiveMaxTextureSize)
-     targetHeight = Math.min(targetHeight, effectiveMaxTextureSize)
- 
-     // 将尺寸调整为2的幂次方，这对WebGL性能更好
-     // 但要保持宽高比，所以基于较大的维度来计算
-     const maxDimension = Math.max(targetWidth, targetHeight)
-     const powerOfTwo = Math.pow(2, Math.ceil(Math.log2(maxDimension)))
- 
-     // 确保 2 的幂次方尺寸不超过有效最大纹理尺寸
-     const safePowerOfTwo = Math.min(powerOfTwo, effectiveMaxTextureSize)
- 
-     // 根据原图宽高比计算最终的纹理尺寸
-     let finalWidth, finalHeight
-     if (aspectRatio >= 1) {
-       // 宽图或正方形，以宽度为基准
-       finalWidth = Math.min(safePowerOfTwo, this.originalImage.width)
-       finalHeight = Math.min(
-         finalWidth / aspectRatio,
-         this.originalImage.height,
-       )
-     } else {
-       // 高图，以高度为基准
-       finalHeight = Math.min(safePowerOfTwo, this.originalImage.height)
-       finalWidth = Math.min(finalHeight * aspectRatio, this.originalImage.width)
-     }
- 
-     // 确保最终尺寸不小于最小要求，但不超过有效最大纹理尺寸
-     finalWidth = Math.max(
-       minWidth,
-       Math.min(finalWidth, effectiveMaxTextureSize),
-     )
-     finalHeight = Math.max(
-       minHeight,
-       Math.min(finalHeight, effectiveMaxTextureSize),
-     )
- 
-     return { width: Math.round(finalWidth), height: Math.round(finalHeight) }
-   }
- 
-   private updateTextureFiltering() {
-     if (!this.texture || !this.originalImage) return
- 
-     const { gl } = this
-     const isSmallImageForFilter =
-       this.originalImage.width < 512 || this.originalImage.height < 512
-     const currentRelativeScale = this.scale / this.getFitToScreenScale()
- 
-     gl.bindTexture(gl.TEXTURE_2D, this.texture)
- 
-     if (isSmallImageForFilter && currentRelativeScale > 1) {
-       // 对于放大的小图片，使用最近邻插值保持锐利
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
-       gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
-     } else {
-       // 其他情况使用线性插值获得平滑效果
+     // Calculate maximum zoom level based on image size and tile size
+     const maxDimension = Math.max(this.imageWidth, this.imageHeight)
+     this.maxZoomLevel = Math.ceil(Math.log2(maxDimension / this.tileSize)) + 2
+ 
+     // Calculate minimum zoom level
+     const minDimension = Math.min(this.canvasWidth, this.canvasHeight)
+     this.minZoomLevel = Math.floor(Math.log2(minDimension / maxDimension)) - 1
+   }
+ 
+   private getTileKey(x: number, y: number, level: number): string {
+     return `${level}_${x}_${y}`
+   }
+ 
+   private getZoomLevelFromScale(scale: number): number {
+     const fitToScreenScale = this.getFitToScreenScale()
+     const relativeScale = scale / fitToScreenScale
+     return Math.round(Math.log2(relativeScale))
+   }
+ 
+   private getScaleFromZoomLevel(level: number): number {
+     const fitToScreenScale = this.getFitToScreenScale()
+     return fitToScreenScale * Math.pow(2, level)
+   }
+ 
+   private getTilesForLevel(level: number): TileCoordinate[] {
+     const levelScale = Math.pow(2, level)
+     const scaledWidth = this.imageWidth * levelScale
+     const scaledHeight = this.imageHeight * levelScale
+ 
+     const tilesX = Math.ceil(scaledWidth / this.tileSize)
+     const tilesY = Math.ceil(scaledHeight / this.tileSize)
+ 
+     const tiles: TileCoordinate[] = []
+     for (let y = 0; y < tilesY; y++) {
+       for (let x = 0; x < tilesX; x++) {
+         tiles.push({ x, y, level })
+       }
+     }
+     return tiles
+   }
+ 
+   private getVisibleTiles(): TileCoordinate[] {
+     const currentLevel = this.getZoomLevelFromScale(this.scale)
+     const visibleTiles: TileCoordinate[] = []
+ 
+     // Get tiles for current level and adjacent levels for smooth transitions
+     for (let level = currentLevel - 1; level <= currentLevel + 1; level++) {
+       if (level < this.minZoomLevel || level > this.maxZoomLevel) continue
+ 
+       const levelScale = Math.pow(2, level)
+       const scaledWidth = this.imageWidth * levelScale
+       const scaledHeight = this.imageHeight * levelScale
+ 
+       // Calculate visible area in image coordinates
+       const viewLeft = -this.translateX / this.scale
+       const viewTop = -this.translateY / this.scale
+       const viewRight = viewLeft + this.canvasWidth / this.scale
+       const viewBottom = viewTop + this.canvasHeight / this.scale
+ 
+       // Convert to tile coordinates for this level
+       const tileLeft = Math.floor((viewLeft * levelScale) / this.tileSize)
+       const tileTop = Math.floor((viewTop * levelScale) / this.tileSize)
+       const tileRight = Math.ceil((viewRight * levelScale) / this.tileSize)
+       const tileBottom = Math.ceil((viewBottom * levelScale) / this.tileSize)
+ 
+       const tilesX = Math.ceil(scaledWidth / this.tileSize)
+       const tilesY = Math.ceil(scaledHeight / this.tileSize)
+ 
+       for (let y = Math.max(0, tileTop); y < Math.min(tilesY, tileBottom); y++) {
+         for (let x = Math.max(0, tileLeft); x < Math.min(tilesX, tileRight); x++) {
+           const tile: TileCoordinate = { x, y, level }
+           
+           // Calculate priority based on distance from current level and screen center
+           const centerX = this.canvasWidth / 2
+           const centerY = this.canvasHeight / 2
+           const tileWorldX = (x * this.tileSize) / levelScale
+           const tileWorldY = (y * this.tileSize) / levelScale
+           const tileScreenX = tileWorldX * this.scale + this.translateX
+           const tileScreenY = tileWorldY * this.scale + this.translateY
+           
+           const distanceFromCenter = Math.sqrt(
+             Math.pow(tileScreenX - centerX, 2) + Math.pow(tileScreenY - centerY, 2)
+           )
+           const levelPriority = level === currentLevel ? 1000 : 500 - Math.abs(level - currentLevel) * 100
+           const priority = levelPriority - distanceFromCenter
+ 
+           visibleTiles.push({ ...tile })
+           
+           // Update tile priority if it exists
+           const key = this.getTileKey(x, y, level)
+           const existingTile = this.tiles.get(key)
+           if (existingTile) {
+             existingTile.priority = priority
+             existingTile.lastUsed = performance.now()
+           }
+         }
+       }
+     }
+ 
+     return visibleTiles
+   }
+ 
+   private updateVisibleTiles() {
+     const visibleTiles = this.getVisibleTiles()
+     
+     // Add visible tiles to load queue if not already loaded or loading
+     for (const tileCoord of visibleTiles) {
+       const key = this.getTileKey(tileCoord.x, tileCoord.y, tileCoord.level)
+       const tile = this.tiles.get(key)
+       
+       if (!tile) {
+         // Create new tile
+         const newTile: Tile = {
+           x: tileCoord.x,
+           y: tileCoord.y,
+           level: tileCoord.level,
+           texture: null,
+           canvas: null,
+           loaded: false,
+           loading: false,
+           lastUsed: performance.now(),
+           priority: 0
+         }
+         this.tiles.set(key, newTile)
+         this.tileLoadQueue.push(tileCoord)
+       } else if (!tile.loaded && !tile.loading) {
+         this.tileLoadQueue.push(tileCoord)
+       }
+     }
+ 
+     // Sort load queue by priority
+     this.tileLoadQueue.sort((a, b) => {
+       const keyA = this.getTileKey(a.x, a.y, a.level)
+       const keyB = this.getTileKey(b.x, b.y, b.level)
+       const tileA = this.tiles.get(keyA)
+       const tileB = this.tiles.get(keyB)
+       return (tileB?.priority || 0) - (tileA?.priority || 0)
+     })
+ 
+     // Start loading tiles if not already loading
+     if (!this.isLoadingTiles && this.tileLoadQueue.length > 0) {
+       this.loadNextTile()
+     }
+ 
+     // Clean up old tiles
+     this.cleanupTiles()
+   }
+ 
+   private async loadNextTile() {
+     if (this.tileLoadQueue.length === 0) {
+       this.isLoadingTiles = false
+       return
+     }
+ 
+     this.isLoadingTiles = true
+     const tileCoord = this.tileLoadQueue.shift()!
+     const key = this.getTileKey(tileCoord.x, tileCoord.y, tileCoord.level)
+     const tile = this.tiles.get(key)
+ 
+     if (!tile || tile.loaded || tile.loading) {
+       // Continue with next tile
+       setTimeout(() => this.loadNextTile(), 0)
+       return
+     }
+ 
+     tile.loading = true
+ 
+     try {
+       await this.createTileTexture(tile)
+       tile.loaded = true
+       tile.loading = false
+       
+       // Trigger a render update
+       this.render()
+     } catch (error) {
+       console.error('Failed to load tile:', error)
+       tile.loading = false
+     }
+ 
+     // Continue loading next tile with a small delay to prevent blocking
+     setTimeout(() => this.loadNextTile(), 1)
+   }
+ 
+   private async createTileTexture(tile: Tile) {
+     if (!this.originalImage) return
+ 
+     const { gl } = this
+     const levelScale = Math.pow(2, tile.level)
+     
+     // Calculate tile bounds in original image coordinates
+     const tileImageX = (tile.x * this.tileSize) / levelScale
+     const tileImageY = (tile.y * this.tileSize) / levelScale
+     const tileImageWidth = Math.min(this.tileSize / levelScale, this.imageWidth - tileImageX)
+     const tileImageHeight = Math.min(this.tileSize / levelScale, this.imageHeight - tileImageY)
+ 
+     // Create canvas for this tile
+     const canvas = document.createElement('canvas')
+     const ctx = canvas.getContext('2d')!
+     
+     // Calculate actual tile size (may be smaller for edge tiles)
+     const actualTileWidth = Math.min(this.tileSize, tileImageWidth * levelScale)
+     const actualTileHeight = Math.min(this.tileSize, tileImageHeight * levelScale)
+     
+     canvas.width = actualTileWidth
+     canvas.height = actualTileHeight
+ 
+     // Configure high-quality rendering
+     ctx.imageSmoothingEnabled = true
+     ctx.imageSmoothingQuality = 'high'
+ 
+     // For pixel art or when zooming in significantly, use nearest neighbor
+     if (levelScale > 2) {
+       ctx.imageSmoothingEnabled = false
+     }
+ 
+     // Draw the tile portion of the image
+     ctx.drawImage(
+       this.originalImage,
+       tileImageX, tileImageY, tileImageWidth, tileImageHeight,
+       0, 0, actualTileWidth, actualTileHeight
+     )
+ 
+     // Create WebGL texture
+     const texture = gl.createTexture()
+     if (!texture) {
+       throw new Error('Failed to create WebGL texture for tile')
+     }
+ 
+     gl.bindTexture(gl.TEXTURE_2D, texture)
+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
+     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
-     }
-   }
- 
-   private debouncedTextureUpdate() {
-     // 检查是否需要更新纹理
-     const scaleChange =
-       Math.abs(this.scale - this.currentTextureScale) /
-       Math.max(this.currentTextureScale, 0.1)
- 
-     // 降低更新阈值，使纹理更及时地响应缩放变化
-     if (scaleChange < 0.3) {
-       // 即使不需要重新创建纹理，也要更新过滤方式
-       this.updateTextureFiltering()
-       return
-     }
- 
-     // 清除之前的防抖调用
-     if (this.textureDebounceId !== null) {
-       clearTimeout(this.textureDebounceId)
-     }
- 
-     // 缩短防抖延迟，使纹理更新更及时
-     const debounceDelay = this.textureDebounceDelay * 0.5
- 
-     // 设置新的防抖调用
-     this.textureDebounceId = setTimeout(() => {
-       this.textureDebounceId = null
-       this.createOptimizedTexture()
-       this.render()
-     }, debounceDelay)
-   }
+ 
+     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas)
+ 
+     tile.texture = texture
+     tile.canvas = canvas
+   }
+ 
+   private cleanupTiles() {
+     if (this.tiles.size <= this.maxCachedTiles) return
+ 
+     // Sort tiles by last used time and priority
+     const sortedTiles = Array.from(this.tiles.entries()).sort((a, b) => {
+       const [, tileA] = a
+       const [, tileB] = b
+       
+       // Prioritize by last used time, then by priority
+       const timeA = tileA.lastUsed
+       const timeB = tileB.lastUsed
+       const priorityA = tileA.priority
+       const priorityB = tileB.priority
+       
+       if (Math.abs(timeA - timeB) < 1000) { // Within 1 second
+         return priorityA - priorityB
+       }
+       return timeA - timeB
+     })
+ 
+     // Remove oldest tiles
+     const tilesToRemove = sortedTiles.slice(0, this.tiles.size - this.maxCachedTiles)
+     
+     for (const [key, tile] of tilesToRemove) {
+       if (tile.texture) {
+         this.gl.deleteTexture(tile.texture)
+       }
+       this.tiles.delete(key)
+     }
+   }
+ 
    private fitImageToScreen() {
      const scaleX = this.canvasWidth / this.imageWidth
      const scaleY = this.canvasHeight / this.imageHeight
      const fitToScreenScale = Math.min(scaleX, scaleY)
  
-     // initialScale 是相对于适应页面大小的比例
      this.scale = fitToScreenScale * this.config.initialScale
- 
-     // Center the image
      this.translateX = 0
      this.translateY = 0
- 
      this.isOriginalSize = false
    }
  
-   // Easing function for smooth animation
    private easeInOutCubic(t: number): number {
      return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
```

```diff
      this.startTranslateY = this.translateY
  
-     // Apply constraints to target position before starting animation
      const tempScale = this.scale
      const tempTranslateX = this.translateX
```

```diff
      this.targetTranslateY = this.translateY
  
-     // Restore current state
      this.scale = tempScale
      this.translateX = tempTranslateX
```

```diff
        : progress
  
-     // Interpolate scale and translation
      this.scale =
        this.startScale + (this.targetScale - this.startScale) * easedProgress
```

```diff
        (this.targetTranslateY - this.startTranslateY) * easedProgress
  
+     this.updateVisibleTiles()
      this.render()
      this.notifyZoomChange()
```

```diff
      } else {
        this.isAnimating = false
-       // Ensure final values are exactly the target values
        this.scale = this.targetScale
        this.translateX = this.targetTranslateX
        this.translateY = this.targetTranslateY
+       this.updateVisibleTiles()
        this.render()
        this.notifyZoomChange()
-       // 动画完成后触发纹理更新
-       this.debouncedTextureUpdate()
      }
    }
  
    private createMatrix(): Float32Array {
-     // Create transformation matrix
-     // 保持所有计算基于CSS尺寸，设备像素比的影响已经在canvas尺寸设置中处理
      const scaleX = (this.imageWidth * this.scale) / this.canvasWidth
      const scaleY = (this.imageHeight * this.scale) / this.canvasHeight
```

```diff
      const fitScale = this.getFitToScreenScale()
  
-     // If current scale is less than or equal to fit-to-screen scale, center the image
      if (this.scale <= fitScale) {
        this.translateX = 0
```

```diff
      }
  
-     // Otherwise, constrain the image within reasonable bounds
      const scaledWidth = this.imageWidth * this.scale
      const scaledHeight = this.imageHeight * this.scale
  
-     // Calculate the maximum allowed translation to keep image edges within viewport
      const maxTranslateX = Math.max(0, (scaledWidth - this.canvasWidth) / 2)
      const maxTranslateY = Math.max(0, (scaledHeight - this.canvasHeight) / 2)
  
-     // Constrain translation
      this.translateX = Math.max(
        -maxTranslateX,
```

```diff
  
    private constrainScaleAndPosition() {
-     // 首先约束缩放倍数
      const fitToScreenScale = this.getFitToScreenScale()
      const absoluteMinScale = fitToScreenScale * this.config.minScale
      const absoluteMaxScale = fitToScreenScale * this.config.maxScale
  
-     // 如果当前缩放超出范围，调整到合理范围内
      if (this.scale < absoluteMinScale) {
        this.scale = absoluteMinScale
```

```diff
      }
  
-     // 然后约束位置
      this.constrainImagePosition()
    }
```

```diff
      const now = performance.now()
  
-     // 如果距离上次渲染时间不足，则使用节流
      if (now - this.lastRenderTime < this.renderThrottleDelay) {
-       // 清除之前的节流调用
        if (this.renderThrottleId !== null) {
          cancelAnimationFrame(this.renderThrottleId)
        }
  
-       // 安排下次渲染
        this.renderThrottleId = requestAnimationFrame(() => {
          this.renderThrottleId = null
```

```diff
      const { gl } = this
  
-     // 确保视口设置正确，使用实际的canvas像素尺寸
      gl.viewport(0, 0, this.canvas.width, this.canvas.height)
- 
-     // 清除为完全透明
      gl.clearColor(0, 0, 0, 0)
      gl.clear(gl.COLOR_BUFFER_BIT)
  
-     if (!this.texture) return
+     if (!this.imageLoaded) return
  
      gl.useProgram(this.program)
  
-     // Set transformation matrix
      const matrixLocation = gl.getUniformLocation(this.program, 'u_matrix')
-     gl.uniformMatrix3fv(matrixLocation, false, this.createMatrix())
- 
+     const tileOffsetLocation = gl.getUniformLocation(this.program, 'u_tileOffset')
+     const tileScaleLocation = gl.getUniformLocation(this.program, 'u_tileScale')
      const imageLocation = gl.getUniformLocation(this.program, 'u_image')
-     gl.uniform1i(imageLocation, 0)
+     const alphaLocation = gl.getUniformLocation(this.program, 'u_alpha')
  
+     gl.uniformMatrix3fv(matrixLocation, false, this.createMatrix())
+     gl.uniform1i(imageLocation, 0)
      gl.activeTexture(gl.TEXTURE0)
-     gl.bindTexture(gl.TEXTURE_2D, this.texture)
- 
+ 
+     // Render tiles
+     const visibleTiles = this.getVisibleTiles()
+     const currentLevel = this.getZoomLevelFromScale(this.scale)
+ 
+     // Group tiles by level for proper alpha blending
+     const tilesByLevel = new Map<number, TileCoordinate[]>()
+     for (const tileCoord of visibleTiles) {
+       const key = this.getTileKey(tileCoord.x, tileCoord.y, tileCoord.level)
+       const tile = this.tiles.get(key)
+       if (tile?.loaded && tile.texture) {
+         if (!tilesByLevel.has(tileCoord.level)) {
+           tilesByLevel.set(tileCoord.level, [])
+         }
+         tilesByLevel.get(tileCoord.level)!.push(tileCoord)
+       }
+     }
+ 
+     // Render tiles starting from lower resolution levels
+     const sortedLevels = Array.from(tilesByLevel.keys()).sort((a, b) => a - b)
+     
+     for (const level of sortedLevels) {
+       const levelTiles = tilesByLevel.get(level)!
+       const levelScale = Math.pow(2, level)
+       
+       // Calculate alpha based on how close this level is to the current level
+       let alpha = 1.0
+       if (level !== currentLevel) {
+         const levelDiff = Math.abs(level - currentLevel)
+         alpha = Math.max(0.3, 1.0 - levelDiff * 0.3)
+       }
+       
+       gl.uniform1f(alphaLocation, alpha)
+ 
+       for (const tileCoord of levelTiles) {
+         const key = this.getTileKey(tileCoord.x, tileCoord.y, tileCoord.level)
+         const tile = this.tiles.get(key)!
+ 
+         // Calculate tile position and scale
+         const tileWorldX = (tileCoord.x * this.tileSize) / levelScale / this.imageWidth
+         const tileWorldY = (tileCoord.y * this.tileSize) / levelScale / this.imageHeight
+         const tileWorldWidth = Math.min(this.tileSize / levelScale, this.imageWidth - (tileCoord.x * this.tileSize) / levelScale) / this.imageWidth
+         const tileWorldHeight = Math.min(this.tileSize / levelScale, this.imageHeight - (tileCoord.y * this.tileSize) / levelScale) / this.imageHeight
+ 
+         // Convert to normalized coordinates (-1 to 1)
+         const tileOffsetX = (tileWorldX - 0.5) * 2
+         const tileOffsetY = (tileWorldY - 0.5) * 2
+         const tileScaleX = tileWorldWidth
+         const tileScaleY = tileWorldHeight
+ 
+         gl.uniform2f(tileOffsetLocation, tileOffsetX, tileOffsetY)
+         gl.uniform2f(tileScaleLocation, tileScaleX, tileScaleY)
+ 
+         gl.bindTexture(gl.TEXTURE_2D, tile.texture)
          gl.drawArrays(gl.TRIANGLES, 0, 6)
+       }
+     }
  
-     // Update debug info if enabled
      if (this.config.debug && this.onDebugUpdate) {
        this.updateDebugInfo()
```

```diff
      const fitToScreenScale = this.getFitToScreenScale()
      const relativeScale = this.scale / fitToScreenScale
+     const currentLevel = this.getZoomLevelFromScale(this.scale)
  
      this.onDebugUpdate({
```

```diff
        translateX: this.translateX,
        translateY: this.translateY,
-       textureSize: this.lastOptimalTextureSize,
+       currentLevel,
+       tilesLoaded: this.tiles.size,
+       tilesInQueue: this.tileLoadQueue.length,
        canvasSize: { width: this.canvasWidth, height: this.canvasHeight },
        imageSize: { width: this.imageWidth, height: this.imageHeight },
        fitToScreenScale,
-       renderCount: performance.now(),
-       textureUpdateCount: this.currentTextureScale,
-       maxTextureSize: this.maxTextureSize,
+       tileSize: this.tileSize,
+       maxCachedTiles: this.maxCachedTiles,
      })
    }
  
    private notifyZoomChange() {
      if (this.onZoomChange) {
-       // 原图缩放比例（相对于图片原始大小）
        const originalScale = this.scale
- 
-       // 相对于页面适应大小的缩放比例
        const fitToScreenScale = this.getFitToScreenScale()
        const relativeScale = this.scale / fitToScreenScale
```

```diff
  
    private setupEventListeners() {
-     // Mouse events
      this.canvas.addEventListener('mousedown', this.boundHandleMouseDown)
      this.canvas.addEventListener('mousemove', this.boundHandleMouseMove)
      this.canvas.addEventListener('mouseup', this.boundHandleMouseUp)
      this.canvas.addEventListener('wheel', this.boundHandleWheel)
      this.canvas.addEventListener('dblclick', this.boundHandleDoubleClick)
- 
-     // Touch events
      this.canvas.addEventListener('touchstart', this.boundHandleTouchStart)
      this.canvas.addEventListener('touchmove', this.boundHandleTouchMove)
```

```diff
      if (this.isAnimating || this.config.panning.disabled) return
  
-     // Stop any ongoing animation when user starts interacting
      this.isAnimating = false
  
```

```diff
  
    private performDoubleClickAction(x: number, y: number) {
-     // Stop any ongoing animation
      this.isAnimating = false
  
```

```diff
  
        if (this.isOriginalSize) {
-         // Animate to fit-to-screen 1x (适应页面大小) with click position as center
          const targetScale = Math.max(
            absoluteMinScale,
            Math.min(absoluteMaxScale, fitToScreenScale),
          )
  
-         // Calculate zoom point relative to current transform
          const zoomX = (x - this.canvasWidth / 2 - this.translateX) / this.scale
          const zoomY = (y - this.canvasHeight / 2 - this.translateY) / this.scale
  
-         // Calculate target translation after zoom
          const targetTranslateX = x - this.canvasWidth / 2 - zoomX * targetScale
          const targetTranslateY = y - this.canvasHeight / 2 - zoomY * targetScale
```

```diff
          this.isOriginalSize = false
        } else {
-         // Animate to original size 1x (原图原始大小) with click position as center
          const targetScale = Math.max(
            absoluteMinScale,
            Math.min(absoluteMaxScale, 1),
-         ) // 1x = 原图原始大小
+         )
  
-         // Calculate zoom point relative to current transform
          const zoomX = (x - this.canvasWidth / 2 - this.translateX) / this.scale
          const zoomY = (y - this.canvasHeight / 2 - this.translateY) / this.scale
  
-         // Calculate target translation after zoom
          const targetTranslateX = x - this.canvasWidth / 2 - zoomX * targetScale
          const targetTranslateY = y - this.canvasHeight / 2 - zoomY * targetScale
```

```diff
        }
      } else {
-       // Zoom mode
        this.zoomAt(x, y, this.config.doubleClick.step)
      }
```

```diff
        const now = Date.now()
  
-       // Check for double-tap
        if (
          !this.config.doubleClick.disabled &&
```

```diff
          Math.abs(touch.clientY - this.lastTouchY) < 50
        ) {
-         // Double-tap detected
          this.handleTouchDoubleTap(touch.clientX, touch.clientY)
-         this.lastTouchTime = 0 // Reset to prevent triple-tap
+         this.lastTouchTime = 0
          return
        }
  
-       // Single touch - prepare for potential drag or single tap
        this.isDragging = true
        this.lastMouseX = touch.clientX
```

```diff
      this.lastTouchDistance = 0
  
-     // Clear any pending touch tap timeout
      if (this.touchTapTimeout) {
        clearTimeout(this.touchTapTimeout)
```

```diff
      const fitToScreenScale = this.getFitToScreenScale()
  
-     // 将相对缩放比例转换为绝对缩放比例进行限制
      const absoluteMinScale = fitToScreenScale * this.config.minScale
      const absoluteMaxScale = fitToScreenScale * this.config.maxScale
  
-     // Limit zoom
      if (newScale < absoluteMinScale || newScale > absoluteMaxScale) return
  
      if (animated && this.config.smooth) {
-       // Calculate zoom point relative to current transform
        const zoomX = (x - this.canvasWidth / 2 - this.translateX) / this.scale
        const zoomY = (y - this.canvasHeight / 2 - this.translateY) / this.scale
  
-       // Calculate target translation after zoom
        const targetTranslateX = x - this.canvasWidth / 2 - zoomX * newScale
        const targetTranslateY = y - this.canvasHeight / 2 - zoomY * newScale
  
        this.startAnimation(newScale, targetTranslateX, targetTranslateY)
      } else {
-       // Calculate zoom point relative to current transform
        const zoomX = (x - this.canvasWidth / 2 - this.translateX) / this.scale
        const zoomY = (y - this.canvasHeight / 2 - this.translateY) / this.scale
  
        this.scale = newScale
  
-       // Adjust translation to keep zoom point fixed
        this.translateX = x - this.canvasWidth / 2 - zoomX * this.scale
        this.translateY = y - this.canvasHeight / 2 - zoomY * this.scale
```

```diff
        this.render()
        this.notifyZoomChange()
- 
-       this.debouncedTextureUpdate()
      }
    }
  
    async copyOriginalImageToClipboard() {
      try {
-       // 获取原始图片
        const response = await fetch(this.originalImageSrc)
        const blob = await response.blob()
  
-       // 检查浏览器是否支持剪贴板API
        if (!navigator.clipboard || !navigator.clipboard.write) {
          console.warn('Clipboard API not supported')
          return
        }
  
-       // 创建ClipboardItem并写入剪贴板
        const clipboardItem = new ClipboardItem({
          [blob.type]: blob,
```

```diff
    }
  
-   // Public methods
    public zoomIn(animated = false) {
      const centerX = this.canvasWidth / 2
```

```diff
      window.removeEventListener('resize', this.boundResizeCanvas)
  
-     // 清理节流相关的资源
      if (this.renderThrottleId !== null) {
        cancelAnimationFrame(this.renderThrottleId)
        this.renderThrottleId = null
      }
  
-     // 清理纹理防抖相关的资源
-     if (this.textureDebounceId !== null) {
-       clearTimeout(this.textureDebounceId)
-       this.textureDebounceId = null
-     }
- 
-     // 清理触摸双击相关的资源
      if (this.touchTapTimeout !== null) {
        clearTimeout(this.touchTapTimeout)
        this.touchTapTimeout = null
      }
  
-     // 清理 WebGL 资源
-     if (this.texture && this.gl) {
-       this.gl.deleteTexture(this.texture)
-       this.texture = null
+     if (this.imageLoaded) {
+       this.cleanupTiles()
      }
    }
```

```diff
      translateX: 0,
      translateY: 0,
-     textureSize: { width: 0, height: 0 },
+     currentLevel: 0,
+     tilesLoaded: 0,
+     tilesInQueue: 0,
      canvasSize: { width: 0, height: 0 },
      imageSize: { width: 0, height: 0 },
      fitToScreenScale: 1,
-     renderCount: 0,
-     textureUpdateCount: 0,
-     maxTextureSize: 0,
+     tileSize: 0,
+     maxCachedTiles: 0,
    })
  
```

```diff
            margin: 0,
            padding: 0,
-           // 对于像素艺术和小图片保持锐利，使用最新的标准属性
            imageRendering: 'pixelated',
          }}
```

```diff
              Image: {debugInfo.imageSize.width}×{debugInfo.imageSize.height}
            </div>
-           <div>
-             Texture: {debugInfo.textureSize.width}×
-             {debugInfo.textureSize.height}
-           </div>
-           <div>Max Texture Size: {debugInfo.maxTextureSize}</div>
+           <div>Current Level: {debugInfo.currentLevel}</div>
+           <div>Tiles Loaded: {debugInfo.tilesLoaded}</div>
+           <div>Tiles in Queue: {debugInfo.tilesInQueue}</div>
+           <div>Tile Size: {debugInfo.tileSize}</div>
+           <div>Max Cached Tiles: {debugInfo.maxCachedTiles}</div>
            <div>Fit Scale: {debugInfo.fitToScreenScale.toFixed(3)}</div>
          </div>
```

---

现在我需要继续完成剩余的代码并修复linter错误：

---

Cancelled

---

现在我需要修复linter错误：

---



---



---

看起来这个错误已经被修复了。现在让我修复其他的linter错误：

---



---

看起来这些错误也已经被修复了。让我检查一下文件的当前状态：

---

Read file: src/components/ui/WebGLImageViewer.tsx

---

现在让我运行一下代码来检查是否还有其他错误：

---

```bash
cd /Users/innei/git/vibe-coding/photo-gallery-site && npm run type-check
```

```
be-coding/photo-gallery-site && npm run type-check        cd /Users/innei/git/vibe-coding/photo-gallery-site    npm
 ERR_PNPM_NO_SCRIPT  Missing script: type-check

Command "type-check" not found.

innei in photo-gallery-site on  main [$!⇡1] via 🎁 v0.0.1 via  v22.15.0 on ☁️  innei@rss3.io at     55d9fff "Rem
ove redundant text from ProgressiveImage component and update default image format in HEIC converter to JPEG for imp
roved compatibility. These changes streamline the user interface and enhance image conversion performance."  by tuko
n479@gmail.com 
at 14:33:28 ❯
```

---

```bash
npx tsc --noEmit
```

```
at 14:33:28 ❯ npx tsc --noEmit
Packages: +1
+
Progress: resolved 1, reused 1, downloaded 0, added 1, done

                                                                               
                This is not the tsc command you are looking for                
                                                                               

To get access to the TypeScript compiler, tsc, from the command line either:

- Use npm install typescript to first add TypeScript to your project before using npx
- Use yarn to avoid accidentally running code from un-installed packages

innei in photo-gallery-site on  main [$!⇡1] via 🎁 v0.0.1 via  v22.15.0 on ☁️  innei@rss3.io at     55d9fff "Rem
ove redundant text from ProgressiveImage component and update default image format in HEIC converter to JPEG for imp
roved compatibility. These changes streamline the user interface and enhance image conversion performance."  by tuko
n479@gmail.com 
at 14:33:32 ❯
```

---

