Loading index.html +4 −4 Changes for index.html: 4 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -95,10 +95,6 @@ @click="fn.resetControls"></button> <label for="button-reset-controls" v-t="controls.reset"></label> </span> <span v-show="phase === 'render'"> <button id="button-export" class="btn-dark btn-icon" @click="fn.setExportMode" v-icon="export"></button> <label for="button-export" v-t="export.button"></label> </span> <div class="export-panel" v-show="phase === 'export'"> <span>X: </span> <span>{{ Math.round(exportParams.area.x) }}</span> Loading @@ -119,6 +115,10 @@ <span>To full</span> <button v-icon="zoom-fit" @click="fn.setExportParams('full')"></button> </div> <span v-show="phase === 'render' || phase === 'export'"> <button id="button-export" class="btn-dark btn-icon" @click="fn.exportImage" v-icon="export"></button> <label for="button-export" v-t="export.button"></label> </span> <div class="description-container"> <div v-if="description"> <h3 v-t="controls.description"></h3> Loading src/app.ts +27 −25 Changes for src/app.ts: 27 added lines, 25 removed lines. Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ import { resizeCanvasToDisplaySize, SHADER_UPDATE_EVENT, shaderUpdateNotify, WebGLCanvasRenderingContext WebGLCanvasRenderingContext, } from './gl-program.ts'; import { ControlUpdateEvent, getStateDefaults, LIGHT_ANGLE_DEFAULTS, State } from './control.ts'; import { Loading Loading @@ -207,7 +207,6 @@ export class App { this.submitCreateMemoryDataset, this.cancelCreateMemoryDataset, this.onCreateMemoryFileInput, this.setExportMode, this.exportImage, this.onExportDialogClose, this.downloadExportImage, Loading Loading @@ -244,7 +243,7 @@ export class App { static get DEFAULT_EXPORT_PARAMS() { return { area: new Rect(0, 0, 0, 0), zoom: 1, dezoom: 0, outerGrab: new Rect(0, 0, 0, 0), innerGrab: new Rect(0, 0, 0, 0), }; Loading Loading @@ -683,17 +682,6 @@ export class App { console.debug('Calculated max render size', this.maxCanvasSize); } private setExportMode() { if (this.rx.phase !== 'render' || !this.maxCanvasSize) return; const source = this.source; if (!source) return; this.rx.phase = 'export'; if (this.rx.exportParams.area.w === 0 || this.rx.exportParams.area.h === 0) { this.setExportParams(); } this.requestRender(); } private setExportParams(from?: 'view' | 'full' | 'auto') { const source = this.source; if (!source) return; Loading @@ -709,20 +697,17 @@ export class App { const bl = this.state.viewport.clipToTexture([-1, -1]); let area = Rect.fromCorners(vec2.floor(tr, tr), vec2.ceil(bl, bl)); area = area.intersection(canvas)!; let zoom = Math.pow( 2, Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))), ); let dezoom = Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))); this.rx.exportParams = { area, zoom, dezoom, outerGrab: area, innerGrab: area, }; } else if (from === 'full') { this.rx.exportParams = { area: canvas, zoom: 1, dezoom: 0, outerGrab: canvas, innerGrab: canvas, }; Loading @@ -731,16 +716,31 @@ export class App { } private exportImage() { const dataset = this.currentDataset?.data; if (!dataset || !this.source) return; const source = this.source; if (!source || !this.currentDataset || !this.maxCanvasSize) return; if (this.rx.phase === 'render') { this.rx.phase = 'export'; if (this.rx.exportParams.area.w === 0 || this.rx.exportParams.area.h === 0) { this.setExportParams(); } this.requestRender(); } else if (this.rx.phase === 'export') { const dataset = this.currentDataset.data; const exportedState = exportValues(this.state); const data: ExportWorkerData = { ...exportedState, lightSource: getLightSourceVec(exportedState), renderArea: this.source.totalSize, exportParams: null, renderArea: source.totalSize, exportParams: this.rx.raw().exportParams, dataset, }; let dezoom = 0; while ( data.exportParams.area.w / Math.pow(2, dezoom) > this.maxCanvasSize[0] || data.exportParams.area.h / Math.pow(2, dezoom) > this.maxCanvasSize[1] ) dezoom += 1; data.exportParams.dezoom = dezoom; this.source = null; DownloadManager.withProgressBar<Blob>( async (prog) => Loading @@ -748,9 +748,9 @@ export class App { const worker = new Worker(exporterUrl, { type: 'module' }); worker.onmessage = (m: MessageEvent<ExportWorkerResult>) => { console.debug('Worker returned:', m.data); const type = m.data.type; if (type === 'finished') { console.debug('Worker returned:', m.data); res(m.data.imageBlob); } else if (type === 'progress') { const valMax = m.data.status.valueMax || [0, undefined]; Loading @@ -776,6 +776,7 @@ export class App { .catch(catchError(false)) .finally(() => this.setDataset(this.lastDataset)); } } public onExportDialogClose(exportImg: HTMLImageElement) { URL.revokeObjectURL(exportImg.src); Loading Loading @@ -807,6 +808,7 @@ export class App { * @param qx horizontal coordinate of the quadrant * @param qy vertical coordinate of the quadrant * @param value position to set the given quadrant to // * @param keepRatio whether aspect ratio should be kept unmodified by the operation */ private setExportWindowQuadrant([qx, qy]: [0 | 1 | 2, 0 | 1 | 2], value: vec2) { if (!this.source) return; Loading src/dataset.ts +4 −2 Changes for src/dataset.ts: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -159,8 +159,10 @@ export class TiledDataset extends Dataset { constructor(data: OptionalType<TiledDataset>, resolve?: string | URL) { super(data); this.dziURL = new URL(data.dziURL, resolve ?? window.location.toString()).href; this.normalsURL = new URL(data.normalsURL, resolve ?? window.location.toString()).href; if (!resolve && !(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)) resolve = window?.location?.toString(); this.dziURL = new URL(data.dziURL, resolve).href; this.normalsURL = new URL(data.normalsURL, resolve).href; this.dziData = data.dziData; } Loading src/gl-program.ts +30 −7 Changes for src/gl-program.ts: 30 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -6,14 +6,14 @@ import exportRectVtxSource from './shaders/exportRect.vert'; import exportRectFrgSource from './shaders/exportRect.frag'; import { mat4, vec2, vec3 } from 'gl-matrix'; import { Controls, CONTROLS_SET_UNIFORMS, State } from './control.ts'; import { DEBUG, Exported, GL } from './utils.ts'; import { DEBUG, Exported, GL, roundTo } from './utils.ts'; import { Rect, TextureSource, TextureTile } from './tiling.ts'; import { ExportParams } from './app.ts'; export type RenderData = Exported<State> & { readonly lightSource: Readonly<vec3>; readonly renderArea: [number, number]; readonly exportParams: ExportParams | null; readonly exportParams: Exported<ExportParams> | null; }; export type WebGLCanvasRenderingContext = WebGLRenderingContext & { canvas: HTMLCanvasElement }; Loading Loading @@ -306,12 +306,35 @@ export class RelightProgram extends GLProgramBase< return output; } public renderArea(source: TextureSource, data: RenderData, area: Rect) { public renderArea(source: TextureSource, data: RenderData, area: Rect, zoom: number) { super.render(data); const wScale = (2 * area.w) / source.totalSize[0] / this.gl.drawingBufferWidth, hScale = (2 * area.h) / source.totalSize[1] / this.gl.drawingBufferHeight, proj = mat4.fromScaling(mat4.create(), [wScale, hScale, 1]); mat4.translate(proj, proj, [-area.x, -area.y, 0]); const wScale = zoom * 2 / this.gl.drawingBufferWidth, hScale = zoom * 2 / this.gl.drawingBufferHeight, proj = mat4.fromScaling(mat4.create(), [wScale, hScale, 1]), offset = [-area.x - area.w / 2, -area.y - area.h / 2, 0] as const; mat4.translate(proj, proj, offset); const points: vec2[] = [ [0, 0], [0.5, 0.5], [1, 1], ]; console.debug( 'Projection:', proj, [wScale, hScale], 'area', area, 'canvas', [this.gl.drawingBufferWidth, this.gl.drawingBufferHeight], 'offset', offset, points .map((p) => [p[0] * area.w + area.x, p[1] * area.h + area.y] as vec2) .map((p): [vec2, vec2] => [p, vec2.transformMat4([0, 0], p, proj)]) .map(([p1, p2]) => ` [${roundTo(p1, 3).join(', ')}] => [${roundTo(p2, 3).join(', ')}]`) .join('\n'), ); this.renderWithProj(source, proj, data); } Loading src/main.ts +6 −1 Changes for src/main.ts: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -3,7 +3,12 @@ import { DownloadManager } from './download.ts'; import en from './lang/en.yaml'; // Import translation YAMLs here to avoid transitively inclucing them in Worker code import fr from './lang/fr.yaml'; import { createApp } from 'petite-vue'; import { catchError } from './utils.ts'; import { catchError, DEBUG } from './utils.ts'; import * as mat from 'gl-matrix'; if (DEBUG){ (window as any).glMat = mat; } DownloadManager.setBar(document.querySelector('progress#load-progress') as HTMLProgressElement); Loading Loading
index.html +4 −4 Changes for index.html: 4 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -95,10 +95,6 @@ @click="fn.resetControls"></button> <label for="button-reset-controls" v-t="controls.reset"></label> </span> <span v-show="phase === 'render'"> <button id="button-export" class="btn-dark btn-icon" @click="fn.setExportMode" v-icon="export"></button> <label for="button-export" v-t="export.button"></label> </span> <div class="export-panel" v-show="phase === 'export'"> <span>X: </span> <span>{{ Math.round(exportParams.area.x) }}</span> Loading @@ -119,6 +115,10 @@ <span>To full</span> <button v-icon="zoom-fit" @click="fn.setExportParams('full')"></button> </div> <span v-show="phase === 'render' || phase === 'export'"> <button id="button-export" class="btn-dark btn-icon" @click="fn.exportImage" v-icon="export"></button> <label for="button-export" v-t="export.button"></label> </span> <div class="description-container"> <div v-if="description"> <h3 v-t="controls.description"></h3> Loading
src/app.ts +27 −25 Changes for src/app.ts: 27 added lines, 25 removed lines. Original line number Diff line number Diff line Loading @@ -6,7 +6,7 @@ import { resizeCanvasToDisplaySize, SHADER_UPDATE_EVENT, shaderUpdateNotify, WebGLCanvasRenderingContext WebGLCanvasRenderingContext, } from './gl-program.ts'; import { ControlUpdateEvent, getStateDefaults, LIGHT_ANGLE_DEFAULTS, State } from './control.ts'; import { Loading Loading @@ -207,7 +207,6 @@ export class App { this.submitCreateMemoryDataset, this.cancelCreateMemoryDataset, this.onCreateMemoryFileInput, this.setExportMode, this.exportImage, this.onExportDialogClose, this.downloadExportImage, Loading Loading @@ -244,7 +243,7 @@ export class App { static get DEFAULT_EXPORT_PARAMS() { return { area: new Rect(0, 0, 0, 0), zoom: 1, dezoom: 0, outerGrab: new Rect(0, 0, 0, 0), innerGrab: new Rect(0, 0, 0, 0), }; Loading Loading @@ -683,17 +682,6 @@ export class App { console.debug('Calculated max render size', this.maxCanvasSize); } private setExportMode() { if (this.rx.phase !== 'render' || !this.maxCanvasSize) return; const source = this.source; if (!source) return; this.rx.phase = 'export'; if (this.rx.exportParams.area.w === 0 || this.rx.exportParams.area.h === 0) { this.setExportParams(); } this.requestRender(); } private setExportParams(from?: 'view' | 'full' | 'auto') { const source = this.source; if (!source) return; Loading @@ -709,20 +697,17 @@ export class App { const bl = this.state.viewport.clipToTexture([-1, -1]); let area = Rect.fromCorners(vec2.floor(tr, tr), vec2.ceil(bl, bl)); area = area.intersection(canvas)!; let zoom = Math.pow( 2, Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))), ); let dezoom = Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))); this.rx.exportParams = { area, zoom, dezoom, outerGrab: area, innerGrab: area, }; } else if (from === 'full') { this.rx.exportParams = { area: canvas, zoom: 1, dezoom: 0, outerGrab: canvas, innerGrab: canvas, }; Loading @@ -731,16 +716,31 @@ export class App { } private exportImage() { const dataset = this.currentDataset?.data; if (!dataset || !this.source) return; const source = this.source; if (!source || !this.currentDataset || !this.maxCanvasSize) return; if (this.rx.phase === 'render') { this.rx.phase = 'export'; if (this.rx.exportParams.area.w === 0 || this.rx.exportParams.area.h === 0) { this.setExportParams(); } this.requestRender(); } else if (this.rx.phase === 'export') { const dataset = this.currentDataset.data; const exportedState = exportValues(this.state); const data: ExportWorkerData = { ...exportedState, lightSource: getLightSourceVec(exportedState), renderArea: this.source.totalSize, exportParams: null, renderArea: source.totalSize, exportParams: this.rx.raw().exportParams, dataset, }; let dezoom = 0; while ( data.exportParams.area.w / Math.pow(2, dezoom) > this.maxCanvasSize[0] || data.exportParams.area.h / Math.pow(2, dezoom) > this.maxCanvasSize[1] ) dezoom += 1; data.exportParams.dezoom = dezoom; this.source = null; DownloadManager.withProgressBar<Blob>( async (prog) => Loading @@ -748,9 +748,9 @@ export class App { const worker = new Worker(exporterUrl, { type: 'module' }); worker.onmessage = (m: MessageEvent<ExportWorkerResult>) => { console.debug('Worker returned:', m.data); const type = m.data.type; if (type === 'finished') { console.debug('Worker returned:', m.data); res(m.data.imageBlob); } else if (type === 'progress') { const valMax = m.data.status.valueMax || [0, undefined]; Loading @@ -776,6 +776,7 @@ export class App { .catch(catchError(false)) .finally(() => this.setDataset(this.lastDataset)); } } public onExportDialogClose(exportImg: HTMLImageElement) { URL.revokeObjectURL(exportImg.src); Loading Loading @@ -807,6 +808,7 @@ export class App { * @param qx horizontal coordinate of the quadrant * @param qy vertical coordinate of the quadrant * @param value position to set the given quadrant to // * @param keepRatio whether aspect ratio should be kept unmodified by the operation */ private setExportWindowQuadrant([qx, qy]: [0 | 1 | 2, 0 | 1 | 2], value: vec2) { if (!this.source) return; Loading
src/dataset.ts +4 −2 Changes for src/dataset.ts: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -159,8 +159,10 @@ export class TiledDataset extends Dataset { constructor(data: OptionalType<TiledDataset>, resolve?: string | URL) { super(data); this.dziURL = new URL(data.dziURL, resolve ?? window.location.toString()).href; this.normalsURL = new URL(data.normalsURL, resolve ?? window.location.toString()).href; if (!resolve && !(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)) resolve = window?.location?.toString(); this.dziURL = new URL(data.dziURL, resolve).href; this.normalsURL = new URL(data.normalsURL, resolve).href; this.dziData = data.dziData; } Loading
src/gl-program.ts +30 −7 Changes for src/gl-program.ts: 30 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -6,14 +6,14 @@ import exportRectVtxSource from './shaders/exportRect.vert'; import exportRectFrgSource from './shaders/exportRect.frag'; import { mat4, vec2, vec3 } from 'gl-matrix'; import { Controls, CONTROLS_SET_UNIFORMS, State } from './control.ts'; import { DEBUG, Exported, GL } from './utils.ts'; import { DEBUG, Exported, GL, roundTo } from './utils.ts'; import { Rect, TextureSource, TextureTile } from './tiling.ts'; import { ExportParams } from './app.ts'; export type RenderData = Exported<State> & { readonly lightSource: Readonly<vec3>; readonly renderArea: [number, number]; readonly exportParams: ExportParams | null; readonly exportParams: Exported<ExportParams> | null; }; export type WebGLCanvasRenderingContext = WebGLRenderingContext & { canvas: HTMLCanvasElement }; Loading Loading @@ -306,12 +306,35 @@ export class RelightProgram extends GLProgramBase< return output; } public renderArea(source: TextureSource, data: RenderData, area: Rect) { public renderArea(source: TextureSource, data: RenderData, area: Rect, zoom: number) { super.render(data); const wScale = (2 * area.w) / source.totalSize[0] / this.gl.drawingBufferWidth, hScale = (2 * area.h) / source.totalSize[1] / this.gl.drawingBufferHeight, proj = mat4.fromScaling(mat4.create(), [wScale, hScale, 1]); mat4.translate(proj, proj, [-area.x, -area.y, 0]); const wScale = zoom * 2 / this.gl.drawingBufferWidth, hScale = zoom * 2 / this.gl.drawingBufferHeight, proj = mat4.fromScaling(mat4.create(), [wScale, hScale, 1]), offset = [-area.x - area.w / 2, -area.y - area.h / 2, 0] as const; mat4.translate(proj, proj, offset); const points: vec2[] = [ [0, 0], [0.5, 0.5], [1, 1], ]; console.debug( 'Projection:', proj, [wScale, hScale], 'area', area, 'canvas', [this.gl.drawingBufferWidth, this.gl.drawingBufferHeight], 'offset', offset, points .map((p) => [p[0] * area.w + area.x, p[1] * area.h + area.y] as vec2) .map((p): [vec2, vec2] => [p, vec2.transformMat4([0, 0], p, proj)]) .map(([p1, p2]) => ` [${roundTo(p1, 3).join(', ')}] => [${roundTo(p2, 3).join(', ')}]`) .join('\n'), ); this.renderWithProj(source, proj, data); } Loading
src/main.ts +6 −1 Changes for src/main.ts: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -3,7 +3,12 @@ import { DownloadManager } from './download.ts'; import en from './lang/en.yaml'; // Import translation YAMLs here to avoid transitively inclucing them in Worker code import fr from './lang/fr.yaml'; import { createApp } from 'petite-vue'; import { catchError } from './utils.ts'; import { catchError, DEBUG } from './utils.ts'; import * as mat from 'gl-matrix'; if (DEBUG){ (window as any).glMat = mat; } DownloadManager.setBar(document.querySelector('progress#load-progress') as HTMLProgressElement); Loading