<!-- Generated by SpecStory -->

# 提供 image width 和 height 的讨论 (2025-06-06 15:49:49)

_**User**_

这里如果提供了 image width 和 height 是不是不需要使用 new Image() 了

---

_**Assistant**_



---

你提出了一个很好的优化点！确实，如果提供了图片的 width 和 height，可以优化加载流程，但不能完全跳过 `new Image()`，因为后续创建纹理时仍需要实际的图片数据。

不过可以这样优化：

---

```diff
    }
  
-   async loadImage(url: string) {
+   async loadImage(url: string, preknownWidth?: number, preknownHeight?: number) {
      this.originalImageSrc = url
      this.isLoadingTexture = true // 开始加载图片
      this.notifyLoadingStateChange(true, '图片加载中...')
+     
+     // 如果提供了预知的尺寸，可以立即设置并开始准备渲染管道
+     if (preknownWidth && preknownHeight) {
+       this.imageWidth = preknownWidth
+       this.imageHeight = preknownHeight
+       
+       // 立即设置缩放和渲染策略
+       this.setupInitialScaling()
+       this.determineRenderingStrategy()
+       
+       console.info(`Using preknown dimensions: ${preknownWidth}×${preknownHeight}`)
+       console.info('Starting parallel image loading for texture creation...')
+     }
+     
      const image = new Image()
      image.crossOrigin = 'anonymous'
```

```diff
        image.onload = async () => {
          try {
-           this.imageWidth = image.width
-           this.imageHeight = image.height
- 
-           // 估算内存需求并决定渲染策略
-           const imagePixels = image.width * image.height
-           const baseMemoryMB = (imagePixels * 4) / (1024 * 1024) // RGBA 基础内存
-           const estimatedMaxMemoryMB = baseMemoryMB * 3 // 估算最多需要的内存（多个LOD级别）
- 
-           console.info(`Image loaded: ${image.width}×${image.height}`)
-           console.info(`Base memory requirement: ${baseMemoryMB.toFixed(1)} MB`)
-           console.info(
-             `Estimated max memory: ${estimatedMaxMemoryMB.toFixed(1)} MB`,
-           )
-           console.info(
-             `Memory budget: ${(this.maxMemoryBudget / 1024 / 1024).toFixed(1)} MB`,
-           )
- 
-           // 决定是否使用瓦片渲染
-           const maxDimension = Math.max(image.width, image.height)
-           const shouldUseTiling =
-             estimatedMaxMemoryMB > this.maxMemoryBudget / (1024 * 1024) ||
-             imagePixels > 50 * 1024 * 1024 || // 50M 像素
-             maxDimension > 8192 // 任一边超过 8K
- 
-           if (shouldUseTiling) {
-             this.useTiledRendering = true
-             console.info(`🧩 Using tiled rendering for large image`)
-             console.info(`Tile size: ${this.tileSize}×${this.tileSize}`)
-             console.info(`Max tiles in memory: ${this.maxTilesInMemory}`)
+           // 如果没有预知尺寸，现在获取
+           if (!preknownWidth || !preknownHeight) {
+             this.imageWidth = image.width
+             this.imageHeight = image.height
+             
+             // 设置缩放和渲染策略
+             this.setupInitialScaling()
+             this.determineRenderingStrategy()
            } else {
-             console.info(`📄 Using standard LOD rendering`)
-           }
- 
-           // 先设置正确的缩放值，再创建纹理
-           if (this.config.centerOnInit) {
-             this.fitImageToScreen()
-           } else {
-             // 即使不居中，也需要将相对缩放转换为绝对缩放
-             const fitToScreenScale = this.getFitToScreenScale()
-             this.scale = fitToScreenScale * this.config.initialScale
+             // 验证预知尺寸是否正确
+             if (image.width !== preknownWidth || image.height !== preknownHeight) {
+               console.warn(`Preknown dimensions mismatch: expected ${preknownWidth}×${preknownHeight}, actual ${image.width}×${image.height}`)
+               this.imageWidth = image.width
+               this.imageHeight = image.height
+               
+               // 重新设置缩放和渲染策略
+               this.setupInitialScaling()
+               this.determineRenderingStrategy()
+             }
            }
  
```

```diff
      })
    }
+ 
+   private setupInitialScaling() {
+     // 先设置正确的缩放值
+     if (this.config.centerOnInit) {
+       this.fitImageToScreen()
+     } else {
+       // 即使不居中，也需要将相对缩放转换为绝对缩放
+       const fitToScreenScale = this.getFitToScreenScale()
+       this.scale = fitToScreenScale * this.config.initialScale
+     }
+   }
+ 
+   private determineRenderingStrategy() {
+     // 估算内存需求并决定渲染策略
+     const imagePixels = this.imageWidth * this.imageHeight
+     const baseMemoryMB = (imagePixels * 4) / (1024 * 1024) // RGBA 基础内存
+     const estimatedMaxMemoryMB = baseMemoryMB * 3 // 估算最多需要的内存（多个LOD级别）
+ 
+     console.info(`Image loaded: ${this.imageWidth}×${this.imageHeight}`)
+     console.info(`Base memory requirement: ${baseMemoryMB.toFixed(1)} MB`)
+     console.info(
+       `Estimated max memory: ${estimatedMaxMemoryMB.toFixed(1)} MB`,
+     )
+     console.info(
+       `Memory budget: ${(this.maxMemoryBudget / 1024 / 1024).toFixed(1)} MB`,
+     )
+ 
+     // 决定是否使用瓦片渲染
+     const maxDimension = Math.max(this.imageWidth, this.imageHeight)
+     const shouldUseTiling =
+       estimatedMaxMemoryMB > this.maxMemoryBudget / (1024 / 1024) ||
+       imagePixels > 50 * 1024 * 1024 || // 50M 像素
+       maxDimension > 8192 // 任一边超过 8K
+ 
+     if (shouldUseTiling) {
+       this.useTiledRendering = true
+       console.info(`🧩 Using tiled rendering for large image`)
+       console.info(`Tile size: ${this.tileSize}×${this.tileSize}`)
+       console.info(`Max tiles in memory: ${this.maxTilesInMemory}`)
+     } else {
+       console.info(`📄 Using standard LOD rendering`)
+     }
+   }
  
    private async createTexture(image: HTMLImageElement) {
```

---

看起来有个小bug，我修复一下内存预算的计算：

---



---

现在还需要更新接口定义以支持可选的width和height参数：

---

<details>
            <summary>Searched codebase "loadImage interface definition" • **25** results</summary>
        
| File | Lines |
|------|-------|
| `apps/web/src/lib/image-loader-manager.ts` | L23-28 |
| `apps/web/src/lib/image-loader-manager.ts` | L17-23 |
| `apps/web/src/lib/image-loader-manager.ts` | L33-39 |
| `packages/webgl-viewer/src/lodWorker.ts` | L1-10 |
| `packages/webgl-viewer/src/interface.ts` | L28-52 |
| `apps/web/src/core/types/photo.ts` | L9-15 |
| `scripts/photo-loader.ts` | L1-21 |
| `apps/web/src/types/photo.ts` | L1-23 |
| `apps/web/src/components/ui/photo-viewer/LoadingIndicator.tsx` | L1-18 |
| `apps/web/src/core/types/photo.ts` | L15-37 |
| `apps/web/src/lib/image-loader-manager.ts` | L1-17 |
| `apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L42-121 |
| `apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L1-42 |
| `apps/web/src/components/ui/photo-viewer/LoadingIndicator.tsx` | L18-23 |
| `packages/webgl-viewer/src/WebGLImageViewerEngine.ts` | L422-475 |
| `packages/webgl-viewer/src/WebGLImageViewerEngine.ts` | L395-422 |
| `apps/web/src/lib/image-loader-manager.ts` | L66-139 |
| `apps/web/src/core/photo/processor.ts` | L1-27 |
| `packages/webgl-viewer/src/WebGLImageViewerEngine.ts` | L89-136 |
| `apps/web/src/components/ui/photo-viewer/LoadingIndicator.tsx` | L23-65 |
| `apps/web/src/core/types/photo.ts` | L42-48 |
| `packages/webgl-viewer/src/interface.ts` | L58-94 |
| `apps/web/src/lib/image-loader-manager.ts` | L28-33 |
| `apps/web/src/lib/image-utils.ts` | L54-79 |
| `apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L121-178 |

</details>

---

让我查看WebGL查看器的接口定义：

---

<details>
            <summary>Searched codebase "WebGLImageViewer loadImage" • **2** results</summary>
        
| File |
|------|
| `undefined` |
| `undefined` |

</details>

---

Read file: packages/webgl-viewer/src/WebGLImageViewer.tsx

---

现在我需要修改WebGLImageViewer组件以支持传递width和height参数：

---

Read file: packages/webgl-viewer/src/interface.ts

---

现在我更新接口定义以包含可选的width和height参数：

---



---

现在更新WebGLImageViewer组件以传递这些参数：

---



---

我需要修复linter错误，将width和height添加到config中：

---



---

现在更新WebGLImageViewerEngine以使用config中的width和height：

---



---

现在我需要更新ProgressiveImage组件以传递图片的width和height：

---



---

让我先检查ProgressiveImage组件的内容：

---

<details>
            <summary>Grep search for "WebGLImageViewer" • **1** files</summary>
        
| File | Line | Match |
|------|------|-------|
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | Lundefined | `import type { WebGLImageViewerRef } from '@photo-gallery/webgl-viewer'` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L1 | `import { WebGLImageViewer } from '@photo-gallery/webgl-viewer'` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L68 | `const transformRef = useRef<WebGLImageViewerRef>(null)` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L197 | `<WebGLImageViewer` |

</details>

---

Read file: apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx

---

现在我看到了WebGLImageViewer的用法，我需要添加width和height参数：

---



---

我需要在WebGLImageViewer组件中使用这些参数：

---



---

现在需要更新调用ProgressiveImage的地方，传递图片的width和height。让我检查用法：

---

<details>
            <summary>Grep search for "ProgressiveImage" • **3** files</summary>
        
| File | Line | Match |
|------|------|-------|
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L19 | `import { ProgressiveImage } from './ProgressiveImage'` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L319 | `<ProgressiveImage` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L18 | `interface ProgressiveImageProps {` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L43 | `export const ProgressiveImage = ({` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L64 | `}: ProgressiveImageProps) => {` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L18 | `interface ProgressiveImageProps {` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L43 | `export const ProgressiveImage = ({` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx` | L64 | `}: ProgressiveImageProps) => {` |

</details>

---

Read file: apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx

---

我看到代码已经在使用width和height了！让我检查imageSize是从哪里来的：

---

<details>
            <summary>Grep search for "imageSize" • **2** files</summary>
        
| File | Line | Match |
|------|------|-------|
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L177 | `const imageSize = getImageDisplaySize()` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L325 | `isCurrentImage ? imageSize.width : undefined` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L328 | `isCurrentImage ? imageSize.height : undefined` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L177 | `const imageSize = getImageDisplaySize()` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L325 | `isCurrentImage ? imageSize.width : undefined` |
| `file:///Users/innei/git/vibe-coding/photo-gallery-site/apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx` | L328 | `isCurrentImage ? imageSize.height : undefined` |

</details>

---

Read file: apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx

---

完美！我看到代码已经在正确地传递图片的原始尺寸了。让我检查最后一个细节，看看currentPhoto.width和currentPhoto.height是否来自PhotoManifest：

---



---

我需要删除未使用的imageSize变量：

---



---

我需要删除或重命名getImageDisplaySize函数：

---



---

现在让我总结一下这次优化的效果：

## 优化总结

你的建议非常正确！我已经实现了这个优化，现在支持提供预知的图片尺寸来优化加载流程：

### 主要变化：

1. **WebGLImageViewerEngine.loadImage()** 新增可选参数：
   ```typescript
   async loadImage(url: string, preknownWidth?: number, preknownHeight?: number)
   ```

2. **优化加载流程**：
   - 如果提供了预知尺寸，立即设置图片尺寸并开始准备渲染管道
   - 并行加载实际图片数据用于纹理创建
   - 验证预知尺寸与实际尺寸是否匹配

3. **界面支持**：
   - `WebGLImageViewerProps` 新增 `width?` 和 `height?` 属性
   - `ProgressiveImage` 组件传递这些参数
   - `PhotoViewer` 使用照片的原始尺寸 `currentPhoto.width/height`

### 优化效果：

1. **更快的视口设置**：提供尺寸后可立即设置正确的缩放和渲染策略
2. **更好的用户体验**：用户可以更早看到正确的视口，不需要等待图片加载完成
3. **并行加载**：图片数据加载与渲染管道准备并行进行
4. **向后兼容**：不提供尺寸时仍按原流程工作

这个优化特别适用于照片画廊场景，因为我们通常在manifest中已经有了图片的尺寸信息，可以有效避免等待图片加载才能设置WebGL渲染器的延迟。

---

_**User**_

canvas 的缩放不正确了，现在初始渲染的不是 fit-screen 的内容，而且还会闪几下

---

_**Assistant**_



---

我看到问题了！当提供预知尺寸时，我们移除了缩放设置，这导致canvas没有正确的初始缩放。让我修复这个问题：

---



---

让我先读取当前文件内容：

---

Read file: packages/webgl-viewer/src/WebGLImageViewerEngine.ts

---

现在我看到了问题。当提供预知尺寸时，我们移除了`setupInitialScaling()`调用，这导致缩放没有被正确设置。让我修复这个问题，并且添加一个标志来避免重复设置：

---



---

现在修复loadImage方法的逻辑：

---



---

现在修复image.onload中的逻辑，避免重复设置缩放：

---



---

最后，在destroy方法中重置这个标志：

---



---

现在的优化修复了以下问题：

## 修复总结

### 问题原因：
1. **缩放未设置**：当提供预知尺寸时，我们移除了`setupInitialScaling()`调用，导致canvas没有正确的初始缩放
2. **时序问题**：缩放设置被延迟到图片加载完成后，造成了初始渲染的不正确和闪烁

### 修复措施：

1. **立即设置缩放**：
   ```typescript
   if (preknownWidth && preknownHeight) {
     // 立即设置正确的缩放和渲染策略
     this.setupInitialScaling()
     this.scalingAlreadySet = true // 标记缩放已设置
     this.determineRenderingStrategy()
   }
   ```

2. **避免重复设置**：
   - 添加`scalingAlreadySet`标志防止重复设置缩放
   - 只有在缩放未设置或尺寸不匹配时才重新设置

3. **保持兼容性**：
   - 没有预知尺寸时仍按原流程工作
   - 尺寸不匹配时会重新设置正确的缩放

### 优化效果：

1. **消除闪烁**：立即设置正确的缩放，避免渲染时的尺寸跳变
2. **正确的fit-screen**：确保初始渲染就是正确的适应屏幕缩放
3. **更好的性能**：避免重复的缩放计算和设置
4. **维持优化**：保持并行加载的性能优势

现在canvas应该能正确显示fit-screen的初始状态，并且不会有闪烁问题了。

---

