Files
bitmappery/tests/unit/math/image-math.spec.ts
Igor Zinken baa00dc644 Add timeline mode for animation support (#83)
### Motivation

In order to be more useful as a spritesheet editor, BitMappery now contains a timeline view, a mode where content can be sub grouped into tiles, where each subsequent tile can be traced over the previous one. Each tile can have multiple layers of content for ease of editing.

### Changes

 * Introduced Document types `default` and `timeline`
 * Introduced LayerRef types (allow grouping Layers)
 * Introduced Timeline view for tile based drawing and tracing
 * Introduced Document background color (omits need to create background layer for each tile in a timeline)
 * Cleaned up some legacy overrides made superfluous by newer dependencies
 * Added document presets to document creation flow

### Commits

* Add rel to Layer structure
* Add Document type
* Initial scaffold for timeline view.
* Added initial utility to manage timelines
* Added action to clone all Layers in a tile group
* Moved Layer cloning to layer-util
* Converted zoom tool option panel to TypeScript
* Added action to add a new tile and layer to the Document
* Added action to remove a tile and its layers from the Document
* Added Layer grouping property to Document structure
* Update type check for test
* Initial timeline panel outline
* Created tiles now match Document dimensions
* Added tile cache
* Allow showing a semi transparent carbon copy trace of the previous tile
* Add animation preview window
* Add fps control to animation preview
* Store metadata property inside Documents (allows storing timeline framerate)
* Optimise mobile view for timeline
* Keep thumbnail ratios when previewing tiles and animations
* Button and animation preview window styling
* use RAF-based animation timing in animation preview
* Changes made to layer content now trigger a re-render of the Group tile
* Update Layer reordering logic to also work with subsets
* Optimised animated GIF export, updated pixel art definitions to be more sensible
* Code cleanups
* Added background color to Documents, omitting the need for a background layer on each animation tile
* Removing temp code, updating renderer factory test
* Update layer styling, added tooltip on drag behaviour
* Added document DPI and size unit to meta data
* Refactored deprecated event property from modal key handler
* Remove unused properties from animation preview window
* Update unit test for renderer factory
* Fix bug where closing modals would trigger click on canvas
* Add presets for all Document types
* Added setting to automatically choose appropriate anti-alias setting
* Invalidate tile and thumbnail caches on resize and crop functions. Adjust spritesheet export behaviour
2026-03-26 18:47:05 +01:00

98 lines
4.0 KiB
TypeScript

import { mockZCanvas } from "../mocks";
mockZCanvas();
import { it, describe, expect } from "vitest";
import {
constrain, isPortrait, isLandscape, isSquare,
scaleToFixedWidth, scaleToFixedHeight,
} from "@/math/image-math";
describe( "Image math utilities", () => {
describe( "When constrainting an image to the maximum supported size in megapixels", () => {
it ( "should not adjust the dimensions of images below this threshold", () => {
expect( constrain( 7999, 7999, 8000 * 8000 )).toEqual({ width: 7999, height: 7999 });
});
it ( "should not adjust the dimensions of images where one side is above the thresholds square root, but the product isn't above the max megapixel", () => {
expect( constrain( 6000, 9000, 8000 * 8000 )).toEqual({ width: 6000, height: 9000 });
});
it ( "should adjust the dimensions of images above this threshold", () => {
expect( constrain( 8001, 8001, 8000 * 8000 )).toEqual({ width: 8000, height: 8000 });
});
it ( "should adjust the dimensions of images above this threshold when they are in portrait ratio", () => {
expect( constrain( 7500, 9000, 8000 * 8000 )).toEqual({ width: 7303, height: 8764 });
});
it ( "should adjust the dimensions of images above this threshold when they are in landscape ratio", () => {
expect( constrain( 9000, 7500, 8000 * 8000 )).toEqual({ width: 8764, height: 7303 });
});
});
describe( "When determining ratios", () => {
const PORTRAIT = { width: 3, height: 4 };
const LANDSCAPE = { width: 4, height: 3 };
const SQUARE = { width: 3, height: 3 };
it( "should correctly identify portrait images", () => {
expect( isPortrait( PORTRAIT.width, PORTRAIT.height )).toBe( true );
expect( isPortrait( LANDSCAPE.width, LANDSCAPE.height )).toBe( false );
expect( isPortrait( SQUARE.width, SQUARE.height )).toBe( false );
});
it( "should correctly identify landscape images", () => {
expect( isLandscape( PORTRAIT.width, PORTRAIT.height )).toBe( false );
expect( isLandscape( LANDSCAPE.width, LANDSCAPE.height )).toBe( true );
expect( isLandscape( SQUARE.width, SQUARE.height )).toBe( false );
});
it( "should correctly identify square images", () => {
expect( isSquare( PORTRAIT.width, PORTRAIT.height )).toBe( false );
expect( isSquare( LANDSCAPE.width, LANDSCAPE.height )).toBe( false );
expect( isSquare( SQUARE.width, SQUARE.height )).toBe( true );
});
});
describe( "When scaling dimensions to match a specific width", () => {
it( "should correctly scale portrait ratios", () => {
expect(
scaleToFixedWidth( 400, 300, 200 )
).toEqual({ width: 200, height: 150 });
});
it( "should correctly scale landscape ratios", () => {
expect(
scaleToFixedWidth( 400, 600, 200 )
).toEqual({ width: 200, height: 300 });
});
it( "should correctly scale square ratios", () => {
expect(
scaleToFixedWidth( 400, 400, 200 )
).toEqual({ width: 200, height: 200 });
});
});
describe( "When scaling dimensions to match a specific height", () => {
it( "should correctly scale portrait ratios", () => {
expect(
scaleToFixedHeight( 400, 300, 150 )
).toEqual({ width: 200, height: 150 });
});
it( "should correctly scale landscape ratios", () => {
expect(
scaleToFixedHeight( 300, 600, 200 )
).toEqual({ width: 100, height: 200 });
});
it( "should correctly scale square ratios", () => {
expect(
scaleToFixedHeight( 400, 400, 200 )
).toEqual({ width: 200, height: 200 });
});
});
});