diff --git a/ledder/PixelList.ts b/ledder/PixelList.ts index 45200f1..5720e06 100644 --- a/ledder/PixelList.ts +++ b/ledder/PixelList.ts @@ -14,7 +14,7 @@ export default class PixelList extends Set { /* Creates a traditional x/y raster, with seperate pixels that each get a copy of the color object. - * (Dont use this if you can do it on the more modern ledder-way where each pixel is an object in a container-list) + * Note that this can be more efficient compared to creating/destroying lost of pixel objects for every frame. * raster[x][y] corresponds to Pixel(x,y) * @param YX swap array layout so raster[y][x] corresponds to Pixel(x,y) * @param xFlip Flip x axis: 0,0 corresponds to Pixel(xMax,0) diff --git a/ledder/animations/Text/Marquee.ts b/ledder/animations/Text/Marquee.ts index 1f5e5b4..e023869 100644 --- a/ledder/animations/Text/Marquee.ts +++ b/ledder/animations/Text/Marquee.ts @@ -14,7 +14,7 @@ import Animator from "../../Animator.js" import FxTwinkle from "../../fx/FxTwinkle.js" import FxColorPattern from "../../fx/FxColorPattern.js" import TheMatrix from "../MovieFx/TheMatrix.js" - +import FxSubpixels from "../../fx/FxSubpixels.js" export default class Marquee extends Animator { @@ -33,9 +33,6 @@ export default class Marquee extends Animator { textPixels.centerV(box) - //target container for scroll-view - // const textBox = new PixelBox(box) - let starsGroup = control.group("Stars", false, false) if (starsGroup.switch('Enabled', false).enabled) { new MovingStars().run(box, scheduler, starsGroup) @@ -51,10 +48,9 @@ export default class Marquee extends Animator { new TheMatrix().run(box, scheduler, theMatrixGroup) } - //add text on top of background stuff - box.add(textPixels) let scrollGroup = control.group("Scrolling") + let rotatorPromise if (scrollGroup.switch('Enabled', true).enabled) { @@ -64,7 +60,17 @@ export default class Marquee extends Animator { scheduler.setFps(fpsControl.value) }) - const rotator = new FxRotate(scheduler, scrollGroup) + if (scrollGroup.switch("Subpixel filtering", false).enabled) { + const subpixelFilter = new FxSubpixels(scheduler, control) + const filteredTextPixels = new PixelBox(box) + box.add(filteredTextPixels) + subpixelFilter.run(textPixels, filteredTextPixels) + } else { + box.add(textPixels) + } + + + const rotator = new FxRotate(scheduler, scrollGroup, -1, 0, 1, 0, 0.01) //show one time or loop? let waitX = 0 @@ -100,8 +106,11 @@ export default class Marquee extends Animator { textBoundBox.xMin = 0 rotatorPromise = rotator.run(textPixels, textBoundBox, waitX, null) + + } else { //no scrolling + box.add(textPixels) textPixels.centerH(box) } diff --git a/ledder/fx/FxRotate.ts b/ledder/fx/FxRotate.ts index e44cc8c..474eeec 100644 --- a/ledder/fx/FxRotate.ts +++ b/ledder/fx/FxRotate.ts @@ -17,13 +17,13 @@ export default class FxRotate extends Fx { intervalRandomizerControl: ControlValue - constructor(scheduler: Scheduler, controlGroup: ControlGroup, xStep = -1, yStep = 0, interval = 2, intervalRandomizer = 0) { + constructor(scheduler: Scheduler, controlGroup: ControlGroup, xStep = -1, yStep = 0, interval = 2, intervalRandomizer = 0, minStep=1) { super(scheduler, controlGroup) this.intervalControl = controlGroup.value('Rotate interval', interval, 1, 60, 1) this.intervalRandomizerControl = controlGroup.value('Rotate interval randomizer', intervalRandomizer, 0, 60, 1, true) - this.xStepControl = controlGroup.value('Rotate X step', xStep, -5, 5, 1) - this.yStepControl = controlGroup.value('Rotate Y step', yStep, -5, 5, 1) + this.xStepControl = controlGroup.value('Rotate X step', xStep, -5, 5, minStep) + this.yStepControl = controlGroup.value('Rotate Y step', yStep, -5, 5, minStep) } diff --git a/ledder/fx/FxSubpixels.ts b/ledder/fx/FxSubpixels.ts new file mode 100644 index 0000000..9dfd23e --- /dev/null +++ b/ledder/fx/FxSubpixels.ts @@ -0,0 +1,101 @@ +import Fx from "../Fx.js" +import ControlGroup from "../ControlGroup.js" +import PixelList from "../PixelList.js" +import Scheduler from "../Scheduler.js" +import PixelBox from "../PixelBox.js" +import {colorBlack} from "../Colors.js" + +/** + * This is an additive filter that combines floating point x,y coordinates to exact coordinates. + * Neighbouring pixels are combined to one by adding the colors together by ratio. + * This is used for Marquees, to blur the steps by slow scrolling, making it smoother. + */ +export default class FxSubpixels extends Fx { + + + constructor(scheduler: Scheduler, controls: ControlGroup) { + super(scheduler, controls) + + + } + + run(sourceList: PixelList, targetBox: PixelBox) { + this.running = true + + const targetXY = targetBox.raster(targetBox, colorBlack) + + + this.scheduler.interval(1, () => { + + //reset all colors to 0,0,0,0 + targetBox.forEachPixel((p) => { + p.color.r = 0 + p.color.g = 0 + p.color.b = 0 + + }) + + + //traverse all the source pixels and update target + sourceList.forEachPixel((p) => { + let x = ~~p.x + let y = ~~p.y + + const factorX=p.x-x + const factorY=p.y-y + + const sourceColor = p.color + + + //left top: + if (x<=targetBox.xMax && y<=targetBox.yMax) { + const targetColor = targetXY[x][y].color + const factor = (1 - factorX) * (1 - factorY) + + targetColor.r = targetColor.r + (sourceColor.r * factor) + targetColor.g = targetColor.g + (sourceColor.g * factor) + targetColor.b = targetColor.b + (sourceColor.b * factor) + } + + //right top: + x=x+1 + if (x<=targetBox.xMax && y<=targetBox.yMax) { + const targetColor = targetXY[x][y].color + const factor = (factorX) * (1 - factorY) + + targetColor.r = targetColor.r + (sourceColor.r * factor) + targetColor.g = targetColor.g + (sourceColor.g * factor) + targetColor.b = targetColor.b + (sourceColor.b * factor) + } + + //right bottom: + y=y+1 + if (x<=targetBox.xMax && y<=targetBox.yMax) { + const targetColor = targetXY[x][y].color + const factor = (factorX) * (factorY) + + targetColor.r = targetColor.r + (sourceColor.r * factor) + targetColor.g = targetColor.g + (sourceColor.g * factor) + targetColor.b = targetColor.b + (sourceColor.b * factor) + } + + //left bottom: + x=x-1 + if (x<=targetBox.xMax && y<=targetBox.yMax) { + const targetColor = targetXY[x][y].color + const factor = (1-factorX) * (factorY) + + targetColor.r = targetColor.r + (sourceColor.r * factor) + targetColor.g = targetColor.g + (sourceColor.g * factor) + targetColor.b = targetColor.b + (sourceColor.b * factor) + } + + }) + + + }) + + return (this.promise) + } +} +