Comparing version 0.1.0 to 0.1.1
# 0.1.1 | ||
* Avoid using the cache if working with smaller numbers of strokes. | ||
* Attempt to prevent stroke width being zero at some locations in thin strokes. | ||
# 0.1.0 | ||
@@ -3,0 +7,0 @@ * Zoom to import/export region just after importing. |
@@ -163,3 +163,3 @@ import { Bezier } from 'bezier-js'; | ||
// If the boundaries have two intersections, increasing the half vector's length could fix this. | ||
if (upperBoundary.intersects(lowerBoundary).length === 2) { | ||
if (upperBoundary.intersects(lowerBoundary).length > 0) { | ||
halfVec = halfVec.times(2); | ||
@@ -166,0 +166,0 @@ } |
@@ -43,3 +43,3 @@ import Editor from './Editor'; | ||
getParent(): ImageNode | null; | ||
getChildrenInRegion(region: Rect2): ImageNode[]; | ||
private getChildrenIntersectingRegion; | ||
getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[]; | ||
@@ -46,0 +46,0 @@ getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[]; |
@@ -110,3 +110,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
} | ||
getChildrenInRegion(region) { | ||
getChildrenIntersectingRegion(region) { | ||
return this.children.filter(child => { | ||
@@ -120,3 +120,3 @@ return child.getBBox().intersects(region); | ||
} | ||
return this.getChildrenInRegion(region); | ||
return this.getChildrenIntersectingRegion(region); | ||
} | ||
@@ -133,3 +133,3 @@ // Returns a list of `ImageNode`s with content (and thus no children). | ||
} | ||
const children = this.getChildrenInRegion(region); | ||
const children = this.getChildrenIntersectingRegion(region); | ||
for (const child of children) { | ||
@@ -136,0 +136,0 @@ result.push(...child.getLeavesIntersectingRegion(region, isTooSmall)); |
@@ -41,3 +41,18 @@ import LineSegment2 from './LineSegment2'; | ||
intersects(other) { | ||
return this.intersection(other) !== null; | ||
// Project along x/y axes. | ||
const thisMinX = this.x; | ||
const thisMaxX = thisMinX + this.w; | ||
const otherMinX = other.x; | ||
const otherMaxX = other.x + other.w; | ||
if (thisMaxX < otherMinX || thisMinX > otherMaxX) { | ||
return false; | ||
} | ||
const thisMinY = this.y; | ||
const thisMaxY = thisMinY + this.h; | ||
const otherMinY = other.y; | ||
const otherMaxY = other.y + other.h; | ||
if (thisMaxY < otherMinY || thisMinY > otherMaxY) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
@@ -47,11 +62,7 @@ // Returns the overlap of this and [other], or null, if no such | ||
intersection(other) { | ||
if (!this.intersects(other)) { | ||
return null; | ||
} | ||
const topLeft = this.topLeft.zip(other.topLeft, Math.max); | ||
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min); | ||
// The intersection can't be outside of this rectangle | ||
if (!this.containsPoint(topLeft) || !this.containsPoint(bottomRight)) { | ||
return null; | ||
} | ||
else if (!other.containsPoint(topLeft) || !other.containsPoint(bottomRight)) { | ||
return null; | ||
} | ||
return Rect2.fromCorners(topLeft, bottomRight); | ||
@@ -58,0 +69,0 @@ } |
@@ -14,3 +14,3 @@ import Mat33 from '../../geometry/Mat33'; | ||
startRender() { | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle++; | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle; | ||
if (!this.allocd) { | ||
@@ -37,2 +37,3 @@ throw new Error('Only alloc\'d canvases can be rendered to'); | ||
this.onBeforeDeallocCallback = newDeallocCallback; | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle; | ||
} | ||
@@ -39,0 +40,0 @@ getLastUsedCycle() { |
@@ -28,13 +28,5 @@ import CacheRecord from './CacheRecord'; | ||
getLeastRecentlyUsedRecord() { | ||
let lruSoFar = null; | ||
for (const rec of this.cacheRecords) { | ||
if (!rec.isAllocd()) { | ||
return rec; | ||
} | ||
if (!lruSoFar || rec.getLastUsedCycle() < lruSoFar.getLastUsedCycle()) { | ||
lruSoFar = rec; | ||
} | ||
} | ||
return lruSoFar; | ||
this.cacheRecords.sort((a, b) => a.getLastUsedCycle() - b.getLastUsedCycle()); | ||
return this.cacheRecords[0]; | ||
} | ||
} |
@@ -25,6 +25,6 @@ import Rect2 from '../../geometry/Rect2'; | ||
if (!this.rootNode) { | ||
// Ensure that the node is just big enough to contain the entire viewport. | ||
const rootNodeSize = visibleRect.maxDimension; | ||
// Adjust the node so that it has the correct aspect ratio | ||
const res = this.partialSharedState.props.blockResolution; | ||
const topLeft = visibleRect.topLeft; | ||
this.rootNode = new RenderingCacheNode(new Rect2(topLeft.x, topLeft.y, rootNodeSize, rootNodeSize), this.getSharedState()); | ||
this.rootNode = new RenderingCacheNode(new Rect2(topLeft.x, topLeft.y, res.x, res.y), this.getSharedState()); | ||
} | ||
@@ -35,4 +35,10 @@ while (!this.rootNode.region.containsRect(visibleRect)) { | ||
this.rootNode = (_a = this.rootNode.smallestChildContaining(visibleRect)) !== null && _a !== void 0 ? _a : this.rootNode; | ||
this.rootNode.renderItems(screenRenderer, [image], viewport); | ||
const visibleLeaves = image.getLeavesIntersectingRegion(viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect)); | ||
if (visibleLeaves.length > this.partialSharedState.props.minComponentsToUseCache) { | ||
this.rootNode.renderItems(screenRenderer, [image], viewport); | ||
} | ||
else { | ||
image.render(screenRenderer, visibleRect); | ||
} | ||
} | ||
} |
@@ -136,3 +136,7 @@ // A cache record with sub-nodes. | ||
for (const item of items) { | ||
if (item.getBBox().maxDimension >= this.region.maxDimension) { | ||
const bbox = item.getBBox(); | ||
if (!bbox.intersects(this.region)) { | ||
continue; | ||
} | ||
if (bbox.maxDimension >= this.region.maxDimension) { | ||
newItems.push(...item.getChildrenOrSelfIntersectingRegion(this.region)); | ||
@@ -150,2 +154,5 @@ } | ||
} | ||
if (debugMode) { | ||
screenRenderer.drawRect(this.region, 0.5 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow }); | ||
} | ||
// Could we render direclty from [this] or do we need to recurse? | ||
@@ -164,3 +171,3 @@ const couldRender = this.renderingWouldBeHighEnoughResolution(viewport); | ||
for (const item of items) { | ||
leaves.push(...item.getLeavesIntersectingRegion(this.region)); | ||
leaves.push(...item.getLeavesIntersectingRegion(this.region, rect => rect.w / this.region.w < 2 / this.cacheState.props.blockResolution.x)); | ||
} | ||
@@ -219,3 +226,3 @@ sortLeavesByZIndex(leaves); | ||
if (debugMode) { | ||
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow }); | ||
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay }); | ||
} | ||
@@ -222,0 +229,0 @@ } |
@@ -15,3 +15,3 @@ import { Vec2 } from '../../geometry/Vec2'; | ||
return renderer instanceof DummyRenderer; | ||
}, blockResolution: Vec2.of(500, 500), cacheSize: 500 * 10 * 4, maxScale: 2, minComponentsPerCache: 0 }, cacheOptions)); | ||
}, blockResolution: Vec2.of(500, 500), cacheSize: 500 * 10 * 4, maxScale: 2, minComponentsPerCache: 0, minComponentsToUseCache: 0 }, cacheOptions)); | ||
return { | ||
@@ -18,0 +18,0 @@ cache, |
@@ -13,2 +13,3 @@ import { Vec2 } from '../../geometry/Vec2'; | ||
minComponentsPerCache: number; | ||
minComponentsToUseCache: number; | ||
} | ||
@@ -15,0 +16,0 @@ export interface PartialCacheState { |
@@ -26,3 +26,3 @@ import CanvasRenderer from './renderers/CanvasRenderer'; | ||
} | ||
const cacheBlockResolution = Vec2.of(500, 500); | ||
const cacheBlockResolution = Vec2.of(600, 600); | ||
this.cache = new RenderingCache({ | ||
@@ -49,4 +49,5 @@ createRenderer: () => { | ||
cacheSize: 500 * 500 * 4 * 200, | ||
maxScale: 1.4, | ||
minComponentsPerCache: 10, | ||
maxScale: 1.5, | ||
minComponentsPerCache: 50, | ||
minComponentsToUseCache: 120, | ||
}); | ||
@@ -53,0 +54,0 @@ this.editor.notifier.on(EditorEventType.DisplayResized, event => { |
{ | ||
"name": "js-draw", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ", | ||
@@ -5,0 +5,0 @@ "main": "dist/src/Editor.js", |
@@ -215,3 +215,3 @@ import { Bezier } from 'bezier-js'; | ||
// If the boundaries have two intersections, increasing the half vector's length could fix this. | ||
if (upperBoundary.intersects(lowerBoundary).length === 2) { | ||
if (upperBoundary.intersects(lowerBoundary).length > 0) { | ||
halfVec = halfVec.times(2); | ||
@@ -218,0 +218,0 @@ } |
@@ -140,3 +140,3 @@ import Editor from './Editor'; | ||
public getChildrenInRegion(region: Rect2): ImageNode[] { | ||
private getChildrenIntersectingRegion(region: Rect2): ImageNode[] { | ||
return this.children.filter(child => { | ||
@@ -151,3 +151,3 @@ return child.getBBox().intersects(region); | ||
} | ||
return this.getChildrenInRegion(region); | ||
return this.getChildrenIntersectingRegion(region); | ||
} | ||
@@ -168,3 +168,3 @@ | ||
const children = this.getChildrenInRegion(region); | ||
const children = this.getChildrenIntersectingRegion(region); | ||
for (const child of children) { | ||
@@ -171,0 +171,0 @@ result.push(...child.getLeavesIntersectingRegion(region, isTooSmall)); |
@@ -151,2 +151,11 @@ | ||
}); | ||
it('division of rectangle', () => { | ||
expect(new Rect2(0, 0, 2, 1).divideIntoGrid(2, 2)).toMatchObject( | ||
[ | ||
new Rect2(0, 0, 1, 0.5), new Rect2(1, 0, 1, 0.5), | ||
new Rect2(0, 0.5, 1, 0.5), new Rect2(1, 0.5, 1, 0.5), | ||
] | ||
); | ||
}); | ||
}); |
@@ -70,3 +70,23 @@ import LineSegment2 from './LineSegment2'; | ||
public intersects(other: Rect2): boolean { | ||
return this.intersection(other) !== null; | ||
// Project along x/y axes. | ||
const thisMinX = this.x; | ||
const thisMaxX = thisMinX + this.w; | ||
const otherMinX = other.x; | ||
const otherMaxX = other.x + other.w; | ||
if (thisMaxX < otherMinX || thisMinX > otherMaxX) { | ||
return false; | ||
} | ||
const thisMinY = this.y; | ||
const thisMaxY = thisMinY + this.h; | ||
const otherMinY = other.y; | ||
const otherMaxY = other.y + other.h; | ||
if (thisMaxY < otherMinY || thisMinY > otherMaxY) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
@@ -77,12 +97,9 @@ | ||
public intersection(other: Rect2): Rect2|null { | ||
if (!this.intersects(other)) { | ||
return null; | ||
} | ||
const topLeft = this.topLeft.zip(other.topLeft, Math.max); | ||
const bottomRight = this.bottomRight.zip(other.bottomRight, Math.min); | ||
// The intersection can't be outside of this rectangle | ||
if (!this.containsPoint(topLeft) || !this.containsPoint(bottomRight)) { | ||
return null; | ||
} else if (!other.containsPoint(topLeft) || !other.containsPoint(bottomRight)) { | ||
return null; | ||
} | ||
return Rect2.fromCorners(topLeft, bottomRight); | ||
@@ -89,0 +106,0 @@ } |
@@ -24,3 +24,3 @@ import Mat33 from '../../geometry/Mat33'; | ||
public startRender(): AbstractRenderer { | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle++; | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle; | ||
if (!this.allocd) { | ||
@@ -49,2 +49,3 @@ throw new Error('Only alloc\'d canvases can be rendered to'); | ||
this.onBeforeDeallocCallback = newDeallocCallback; | ||
this.lastUsedCycle = this.cacheState.currentRenderingCycle; | ||
} | ||
@@ -51,0 +52,0 @@ |
@@ -42,15 +42,5 @@ import { BeforeDeallocCallback, PartialCacheState } from './types'; | ||
private getLeastRecentlyUsedRecord(): CacheRecord|null { | ||
let lruSoFar: CacheRecord|null = null; | ||
for (const rec of this.cacheRecords) { | ||
if (!rec.isAllocd()) { | ||
return rec; | ||
} | ||
if (!lruSoFar || rec.getLastUsedCycle() < lruSoFar.getLastUsedCycle()) { | ||
lruSoFar = rec; | ||
} | ||
} | ||
return lruSoFar; | ||
this.cacheRecords.sort((a, b) => a.getLastUsedCycle() - b.getLastUsedCycle()); | ||
return this.cacheRecords[0]; | ||
} | ||
} |
@@ -13,3 +13,3 @@ /* @jest-environment jsdom */ | ||
describe('RenderingCache', () => { | ||
const testPath = Path.fromString('M0,0 l100,500 l-20,20'); | ||
const testPath = Path.fromString('M0,0 l100,500 l-20,20 L-100,-100'); | ||
const testStroke = new Stroke([ testPath.toRenderable({ fill: Color4.purple }) ]); | ||
@@ -16,0 +16,0 @@ |
@@ -40,7 +40,8 @@ import { ImageNode } from '../../EditorImage'; | ||
if (!this.rootNode) { | ||
// Ensure that the node is just big enough to contain the entire viewport. | ||
const rootNodeSize = visibleRect.maxDimension; | ||
// Adjust the node so that it has the correct aspect ratio | ||
const res = this.partialSharedState.props.blockResolution; | ||
const topLeft = visibleRect.topLeft; | ||
this.rootNode = new RenderingCacheNode( | ||
new Rect2(topLeft.x, topLeft.y, rootNodeSize, rootNodeSize), | ||
new Rect2(topLeft.x, topLeft.y, res.x, res.y), | ||
this.getSharedState() | ||
@@ -55,4 +56,10 @@ ); | ||
this.rootNode = this.rootNode!.smallestChildContaining(visibleRect) ?? this.rootNode; | ||
this.rootNode!.renderItems(screenRenderer, [ image ], viewport); | ||
const visibleLeaves = image.getLeavesIntersectingRegion(viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect)); | ||
if (visibleLeaves.length > this.partialSharedState.props.minComponentsToUseCache) { | ||
this.rootNode!.renderItems(screenRenderer, [ image ], viewport); | ||
} else { | ||
image.render(screenRenderer, visibleRect); | ||
} | ||
} | ||
} |
@@ -175,3 +175,8 @@ | ||
for (const item of items) { | ||
if (item.getBBox().maxDimension >= this.region.maxDimension) { | ||
const bbox = item.getBBox(); | ||
if (!bbox.intersects(this.region)) { | ||
continue; | ||
} | ||
if (bbox.maxDimension >= this.region.maxDimension) { | ||
newItems.push(...item.getChildrenOrSelfIntersectingRegion(this.region)); | ||
@@ -190,2 +195,6 @@ } else { | ||
if (debugMode) { | ||
screenRenderer.drawRect(this.region, 0.5 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow }); | ||
} | ||
// Could we render direclty from [this] or do we need to recurse? | ||
@@ -203,3 +212,7 @@ const couldRender = this.renderingWouldBeHighEnoughResolution(viewport); | ||
for (const item of items) { | ||
leaves.push(...item.getLeavesIntersectingRegion(this.region)); | ||
leaves.push( | ||
...item.getLeavesIntersectingRegion( | ||
this.region, rect => rect.w / this.region.w < 2 / this.cacheState.props.blockResolution.x, | ||
) | ||
); | ||
} | ||
@@ -272,3 +285,3 @@ sortLeavesByZIndex(leaves); | ||
if (debugMode) { | ||
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow }); | ||
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay }); | ||
} | ||
@@ -275,0 +288,0 @@ } |
@@ -27,2 +27,3 @@ import { Vec2 } from '../../geometry/Vec2'; | ||
minComponentsPerCache: 0, | ||
minComponentsToUseCache: 0, | ||
...cacheOptions | ||
@@ -29,0 +30,0 @@ }); |
@@ -24,2 +24,6 @@ import { Vec2 } from '../../geometry/Vec2'; | ||
minComponentsPerCache: number; | ||
// Minimum number of strokes/etc. to use the cache to render, isntead of | ||
// rendering directly. | ||
minComponentsToUseCache: number; | ||
} | ||
@@ -26,0 +30,0 @@ |
@@ -34,3 +34,3 @@ import AbstractRenderer from './renderers/AbstractRenderer'; | ||
const cacheBlockResolution = Vec2.of(500, 500); | ||
const cacheBlockResolution = Vec2.of(600, 600); | ||
this.cache = new RenderingCache({ | ||
@@ -58,4 +58,5 @@ createRenderer: () => { | ||
cacheSize: 500 * 500 * 4 * 200, | ||
maxScale: 1.4, | ||
minComponentsPerCache: 10, | ||
maxScale: 1.5, | ||
minComponentsPerCache: 50, | ||
minComponentsToUseCache: 120, | ||
}); | ||
@@ -62,0 +63,0 @@ |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
734725
15305