Commit c90ee20c authored by Laureline Nevin's avatar Laureline Nevin ⌨️
Browse files

Improved export window size limits handling

parent bbeb7e60
Loading
Loading
Loading
Loading
+12 −7
Changes for index.html: 12 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -101,18 +101,22 @@
        </span>
        <div class="export-panel" v-show="phase === 'export'">
            <span>X: </span>
            <span>{{ Math.round(exportParams.offset[0]) }}</span>
            <span>{{ Math.round(exportParams.area.x) }}</span>
            <span>Y: </span>
            <span>{{ Math.round(exportParams.offset[1]) }}</span>
            <span>{{ Math.round(exportParams.area.y) }}</span>
            <span>Width: </span>
            <span>{{ Math.round(exportParams.size[0]) }}</span>
            <span>{{ Math.round(exportParams.area.w) }}</span>
            <span>Height: </span>
            <span>{{ Math.round(exportParams.size[1]) }}</span>
            <span>{{ Math.round(exportParams.area.h) }}</span>
            <span>Inner: </span>
            <span>{{ `${exportParams.innerGrab}` }}</span>
            <span>Outer: </span>
            <span>{{ `${exportParams.outerGrab}` }}</span>
            <span>Reset</span>
            <button v-icon="zoom-fit" @click="fn.setExportParams()"></button>
            <span>Reset to view</span>
            <span>To view</span>
            <button v-icon="zoom-fit" @click="fn.setExportParams('view')"></button>
            <span>Reset to full</span>
            <span>To full</span>
            <button v-icon="zoom-fit" @click="fn.setExportParams('full')"></button>
        </div>
        <div class="description-container">
@@ -126,7 +130,8 @@
            <canvas id="ivoire-lightball"></canvas>
            <div class="reset-button-container">
                <button class="btn-dark btn-icon" @click="app.resetLightball()"
                        :title="t('controls.resetlightball')" v-icon="reset" v-if="phase==='render'"></button>
                        :title="t('controls.resetlightball')" v-icon="reset"
                        v-if="phase==='render' || phase==='export'"></button>
            </div>
        </div>
    </div>
+60 −32
Changes for src/app.ts: 60 added lines, 32 removed lines.
Original line number Diff line number Diff line
@@ -17,8 +17,9 @@ import {
    exportValues,
    getLightSourceVec,
    matToString,
    resetMessage,
    roundTo,
    VERBOSE
    VERBOSE,
} from './utils.ts';
import { DownloadManager } from './download.ts';
import { Dataset, MemoryDataset, SingleDataset, TiledDataset } from './dataset.ts';
@@ -66,6 +67,7 @@ export class App {

    public static readonly LANGS = ['en', 'fr'] as const;
    public static readonly DEFLANG = 'en';
    public static readonly EXPORT_WINDOW_GRAB_MARGIN = 50;
    private static _app?: App;

    public static get app() {
@@ -73,6 +75,10 @@ export class App {
        return this._app;
    }

    public static get appInitialized(): boolean {
        return this._app !== undefined;
    }

    public constructor(translations: Record<string, Record<string, any>>) {
        if (App._app) throw new Error(); // There can be only one

@@ -96,6 +102,7 @@ export class App {
                try {
                    this.setPrograms();
                    this.requestRender();
                    resetMessage();
                } catch (err: any) {
                    catchError(false, err);
                }
@@ -236,9 +243,10 @@ export class App {

    static get DEFAULT_EXPORT_PARAMS() {
        return {
            size: [0, 0] as [number, number],
            offset: [0, 0] as [number, number],
            area: new Rect(0, 0, 0, 0),
            zoom: 1,
            outerGrab: new Rect(0, 0, 0, 0),
            innerGrab: new Rect(0, 0, 0, 0),
        };
    }

@@ -442,6 +450,15 @@ export class App {
        let output = `Rendered at ${Date.now()}\n`;

        if (this.source && (this.rx.phase === 'render' || this.rx.phase === 'export')) {
            if (this.rx.phase === 'export') {
                const margin = Math.floor(App.EXPORT_WINDOW_GRAB_MARGIN / Math.min(this.state.viewport.zoom, 1)),
                    area = this.rx.raw().exportParams.area,
                    xMargin = Math.min(margin, Math.floor(area.w - margin * 1.5)),
                    yMargin = Math.min(margin, Math.floor(area.h - margin * 1.5));
                const inner = area.grow(-xMargin, -yMargin);
                this.rx.exportParams.innerGrab = inner;
                this.rx.exportParams.outerGrab = inner.grow(margin * 1.5);
            }
            const exportedState = exportValues(this.state);
            let data: RenderData = {
                ...exportedState,
@@ -671,7 +688,7 @@ export class App {
        const source = this.source;
        if (!source) return;
        this.rx.phase = 'export';
        if (this.rx.exportParams.size[0] === 0 || this.rx.exportParams.size[1] === 0) {
        if (this.rx.exportParams.area.w === 0 || this.rx.exportParams.area.h === 0) {
            this.setExportParams();
        }
        this.requestRender();
@@ -686,26 +703,28 @@ export class App {
                (source.totalSize[0] > this.maxCanvasSize[0] || source.totalSize[1] > this.maxCanvasSize[1])
                    ? 'view'
                    : 'full';
        let canvas = new Rect(0, 0, ...source.totalSize);
        if (from === 'view') {
            const tr = this.state.viewport.clipToTexture([1, 1]);
            const bl = this.state.viewport.clipToTexture([-1, -1]);
            let canvas = new Rect(0, 0, ...source.totalSize),
                area = Rect.fromCorners(vec2.floor(tr, tr), vec2.ceil(bl, bl));
            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)))),
            );
            this.rx.exportParams = {
                size: [area.w, area.h],
                offset: [area.x, area.y],
                area,
                zoom,
                outerGrab: area,
                innerGrab: area,
            };
        } else if (from === 'full') {
            this.rx.exportParams = {
                size: [...source.totalSize],
                offset: [0, 0],
                area: canvas,
                zoom: 1,
                outerGrab: canvas,
                innerGrab: canvas,
            };
        } else throw new Error(`Unknown argument from = ${from}`);
        this.requestRender();
@@ -779,61 +798,70 @@ export class App {
     * @param y vertical coordinate of the quadrant
     */
    private getExportWindowQuadrant([x, y]: [0 | 1 | 2, 0 | 1 | 2]): vec2 {
        const params = this.rx.exportParams;
        return [params.offset[0] + (x * params.size[0]) / 2, params.offset[1] + (y * params.size[1]) / 2];
        const area = this.rx.exportParams.area;
        return [area.x + (x * area.w) / 2, area.y + (y * area.h) / 2];
    }

    /**
     * Sets the coordinates of the given quadrant of the export window divided into thirds on both axes
     * @param x horizontal coordinate of the quadrant
     * @param y vertical coordinate of the quadrant
     * @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;
        const curVal = this.getExportWindowQuadrant([qx, qy]);
        const params = this.rx.exportParams;
        const diff = vec2.sub(curVal, value, curVal),
            xy = this.rx.exportParams.offset, wh = this.rx.exportParams.size,
            { xy, wh } = params.area,
            total = this.source.totalSize;

        if (qx === 1 && qy === 1) {
            // Center
            this.rx.exportParams.offset = [
            params.area = new Rect(
                clamp(Math.round(xy[0] + diff[0]), 0, total[0] - wh[0]),
                clamp(Math.round(xy[1] + diff[1]), 0, total[1] - wh[1])];
                clamp(Math.round(xy[1] + diff[1]), 0, total[1] - wh[1]),
                ...wh,
            );
            return;
        }
        // Switch from offset/size to start/end coordinates
        let xyMin = [xy[0], xy[1]] as [number, number],
            xyMax = [xy[0] + wh[0], xy[1] + wh[1]] as [number, number];
        if (qx === 1 || qy === 1) { // Middle column or row
        if (qx === 1 || qy === 1) {
            // Middle column or row
            const ax = qy === 1 ? 0 : 1;
            if (qx === 0 || qy === 0) { // Left or bottom
            if (qx === 0 || qy === 0) {
                // Left or bottom
                xyMin[ax] = clamp(Math.round(xyMin[ax] + diff[ax]), 0, xyMax[ax] - 1);
            } else { // Right or top
            } else {
                // Right or top
                xyMax[ax] = clamp(Math.round(xyMax[ax] + diff[ax]), xyMin[ax] + 1, total[ax]);
            }
        } else {
            // Corner
            for (const ax of [0, 1] as const) {
                if ([qx, qy][ax] === 0) { // Left or bottom
                if ([qx, qy][ax] === 0) {
                    // Left or bottom
                    xyMin[ax] = clamp(Math.round(xyMin[ax] + diff[ax]), 0, xyMax[ax] - 1);
                } else { // Right or top
                } else {
                    // Right or top
                    xyMax[ax] = clamp(Math.round(xyMax[ax] + diff[ax]), xyMin[ax] + 1, total[ax]);
                }
            }
        }
        this.rx.exportParams.offset = xyMin;
        this.rx.exportParams.size = [xyMax[0] - xyMin[0], xyMax[1] - xyMin[1]];
        params.area = Rect.fromCorners(xyMin, xyMax);
    }

    private getQuadrantCursor(xy: [0 | 1 | 2, 0 | 1 | 2] | null): string | null {
        return xy && [
        return (
            xy &&
            [
                ['sw-resize', 'w-resize', 'nw-resize'],
                ['s-resize', 'move', 'n-resize'],
                ['se-resize', 'e-resize', 'ne-resize'],
        ][xy[0]][xy[1]];
            ][xy[0]][xy[1]]
        );
    }

    private bindPointerEvents() {
@@ -898,12 +926,12 @@ export class App {

            const exportWindowPart = (pos: vec2): [0 | 1 | 2, 0 | 1 | 2] | null => {
                if (this.rx.phase !== 'export' || lightball) return null;
                const [x, y] = this.rx.exportParams.offset,
                    [w, h] = this.rx.exportParams.size;
                if (!new Rect(x, y, w, h).includes(pos)) return null;
                const exPar = this.rx.raw().exportParams;
                if (!exPar.outerGrab.includes(pos)) return null;

                return [
                    pos[0] < x + w / 3 ? 0 : pos[0] < x + (w * 2) / 3 ? 1 : 2,
                    pos[1] < y + h / 3 ? 0 : pos[1] < y + (h * 2) / 3 ? 1 : 2,
                    pos[0] >= exPar.innerGrab.x ? (pos[0] >= exPar.innerGrab.x2 ? 2 : 1) : 0,
                    pos[1] >= exPar.innerGrab.y ? (pos[1] >= exPar.innerGrab.y2 ? 2 : 1) : 0,
                ];
            };

+32 −11
Changes for src/gl-program.ts: 32 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -428,11 +428,7 @@ export class LightballProgram extends GLProgramBase<
            circle = vec3.fromValues(center[0], center[1], radius),
            alpha =
                LightballProgram.ALPHA[
                    data.pointerState.overLightball
                        ? data.pointerState.dragging
                            ? 'active'
                            : 'hover'
                        : 'inactive'
                    data.pointerState.overLightball ? (data.pointerState.dragging ? 'active' : 'hover') : 'inactive'
                ];

        gl.uniformMatrix4fv(this.uniformLocations.projectionMatrix, false, projection);
@@ -457,6 +453,7 @@ export class ExportRectProgram extends GLProgramBase<
    [keyof typeof ExportRectProgram.attributes],
    [keyof typeof ExportRectProgram.uniformNames]
> {
    private static readonly NONE_GRAB_RECT: readonly [number, number, number, number] = [0, 0, -1, -1];
    public static readonly PARAMS = {
        cornerRadius: 25,
        lineSize: 1,
@@ -468,8 +465,9 @@ export class ExportRectProgram extends GLProgramBase<
    static uniformNames = {
        color: 'uColor',
        exportRect: 'uExportRect',
        windowPartHover: 'uWindowPartHover',
        grabRect: 'uGrabRect',
        lineWidth: 'uLineWidth',
        grabStatus: 'uGrabStatus',
        projectionMatrix: 'uProjectionMatrix',
        renderRect: 'uRenderRect',
    };
@@ -504,11 +502,34 @@ export class ExportRectProgram extends GLProgramBase<

        gl.uniformMatrix4fv(this.uniformLocations.projectionMatrix, false, data.viewport.projectionMat);

        let grabRect = ExportRectProgram.NONE_GRAB_RECT,
            grabStatus = 0;
        if (data.pointerState.exportWindowHover) {
            const windowPartVal = [...data.pointerState.exportWindowHover!, data.pointerState.dragging ? 2 : 1];
            gl.uniform3iv(this.uniformLocations.windowPartHover, windowPartVal);
        } else gl.uniform3i(this.uniformLocations.windowPartHover, -1, -1, 0);
        gl.uniform4f(this.uniformLocations.exportRect, ...data.exportParams!.offset, ...data.exportParams!.size);
            const quad = data.pointerState.exportWindowHover,
                xBnds = [
                    data.exportParams.outerGrab.xmin,
                    data.exportParams.innerGrab.xmin,
                    data.exportParams.innerGrab.xmax,
                    data.exportParams.outerGrab.xmax,
                ],
                yBnds = [
                    data.exportParams.outerGrab.ymin,
                    data.exportParams.innerGrab.ymin,
                    data.exportParams.innerGrab.ymax,
                    data.exportParams.outerGrab.ymax,
                ];
            grabRect = [
                xBnds[quad[0]],
                yBnds[quad[1]],
                xBnds[quad[0] + 1] - xBnds[quad[0]],
                yBnds[quad[1] + 1] - yBnds[quad[1]],
            ];
            grabStatus = data.pointerState.dragging ? 2 : 1;
        }

        gl.uniform4fv(this.uniformLocations.exportRect, data.exportParams.area.xywh);
        gl.uniform4fv(this.uniformLocations.grabRect, grabRect);
        gl.uniform1i(this.uniformLocations.grabStatus, grabStatus);
        gl.uniform4f(this.uniformLocations.renderRect, 0, 0, ...data.renderArea);
        gl.uniform1f(this.uniformLocations.lineWidth, lineSize);
        gl.uniform4fv(this.uniformLocations.color, color);
@@ -520,7 +541,7 @@ export class ExportRectProgram extends GLProgramBase<

        output +=
            `Area: ${area}\n  color: ${color}\n  lineSize: ${lineSize}\n` +
            `  exportRect: ${new Rect(...data.exportParams.offset, ...data.exportParams.size)}\n` +
            `  exportRect: ${data.exportParams.area}\n` +
            `  renderRect: ${new Rect(0, 0, ...data.renderArea)}\n`;

        return output;
+11 −13
Changes for src/shaders/exportRect.frag: 11 added lines, 13 removed lines.
Original line number Diff line number Diff line
precision mediump float;

uniform ivec3 uWindowPartHover;
uniform mediump vec4 uColor;
uniform mediump vec4 uRenderRect;
uniform mediump vec4 uExportRect;
uniform mediump vec4 uGrabRect;
uniform mediump int uGrabStatus;
uniform mediump float uLineWidth;

varying mediump vec2 vVertexCoord;
@@ -16,25 +17,22 @@ float rectDist(vec2 pos, vec4 rect) {

void main(void) {
    float dist = rectDist(vVertexCoord, uExportRect);
    float grabDist = uGrabStatus > 0 ? rectDist(vVertexCoord, uGrabRect) : 1000000.;
    bool inRender = bool(rectDist(vVertexCoord, uRenderRect) <= 0.);
    if (dist < 0.) {
        vec4 windowPart = vec4(uExportRect.xy + vec2(uWindowPartHover.xy) * uExportRect.zw / 3.,
            uExportRect.zw / 3.);
        if (uWindowPartHover.z != 0 && rectDist(vVertexCoord, windowPart) <= 0.) {
            // Export window quadrant hover
            gl_FragColor = vec4(0., 0., 0., float(uWindowPartHover.z) * .2);
        } else {
            discard; // Inside export rectangle
        }
    } else if (dist <= uLineWidth * 4.) {
    bool onGrabBorder = bool(grabDist <= 0. && grabDist > -uLineWidth * 2.);
    if (dist >= 0. && dist <= uLineWidth * 4. && !onGrabBorder) {
        gl_FragColor = uColor;
        if (!inRender) // Full opacity outside of render area
        gl_FragColor.a = 1.;
    } else if (inRender) {
    } else if (grabDist <= 0.) {
        // Export window quadrant hover
        gl_FragColor = vec4(0., 0., 0.,
            float(uGrabStatus) * (onGrabBorder ? .4 : .2));
    } else if (dist > 0. && inRender) {
        // In render area, outside of export window
        gl_FragColor = vec4(0., 0., 0., mod((vVertexCoord.x + vVertexCoord.y) / (uLineWidth * 8.), 2.) > 1. ? .2 : .1);
    } else {
        // Outside of render area
        // Outside of render area or inside export window
        discard;
    }
}
 No newline at end of file
+3 −5
Changes for src/style.sass: 3 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -143,11 +143,9 @@ body

	.export-panel
		display: grid
		grid-template-columns: 1fr 1fr


.title
	margin-bottom: 40px
		grid-template-columns: auto 1fr
		grid-column-gap: 1rem
		justify-items: start


.drop-shadow
Loading