From d73966410fc18ddcd4918b5a53b68ba95cd5ae3e Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Wed, 5 Mar 2025 11:38:43 -0600 Subject: [PATCH 01/14] check cliprect changes earlier --- flixel/FlxSprite.hx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flixel/FlxSprite.hx b/flixel/FlxSprite.hx index a0daab5ef8..875d02e3d9 100644 --- a/flixel/FlxSprite.hx +++ b/flixel/FlxSprite.hx @@ -803,16 +803,16 @@ class FlxSprite extends FlxObject */ override public function draw():Void { + checkClipRect(); + checkEmptyFrame(); - + if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return; - + if (dirty) // rarely calcFrame(useFramePixels); - checkClipRect(); - for (camera in getCamerasLegacy()) { if (!camera.visible || !camera.exists || !isOnScreen(camera)) @@ -839,7 +839,8 @@ class FlxSprite extends FlxObject */ function checkClipRect() { - if ((clipRect == null && Math.isNaN(_lastClipRect.x)) + if (frames == null + || (clipRect == null && Math.isNaN(_lastClipRect.x)) || (clipRect != null && clipRect.equals(_lastClipRect))) return; From 1d5821bf7075f1849fba4661856e2eaa65e0cc1d Mon Sep 17 00:00:00 2001 From: George Kurelic Date: Wed, 5 Mar 2025 13:45:24 -0600 Subject: [PATCH 02/14] trigger CI again --- flixel/FlxSprite.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixel/FlxSprite.hx b/flixel/FlxSprite.hx index 875d02e3d9..e11d8935de 100644 --- a/flixel/FlxSprite.hx +++ b/flixel/FlxSprite.hx @@ -817,12 +817,12 @@ class FlxSprite extends FlxObject { if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue; - + if (isSimpleRender(camera)) drawSimple(camera); else drawComplex(camera); - + #if FLX_DEBUG FlxBasic.visibleCount++; #end From 6e2ea88644782edaa030290aefaea6d9bc7d22d0 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 12:55:19 -0600 Subject: [PATCH 03/14] optional alpha arg --- flixel/util/FlxColor.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixel/util/FlxColor.hx b/flixel/util/FlxColor.hx index 8c6328b84e..e3fe2eb9fd 100644 --- a/flixel/util/FlxColor.hx +++ b/flixel/util/FlxColor.hx @@ -517,7 +517,7 @@ abstract FlxColor(Int) from Int from UInt to Int to UInt * @param Alpha How opaque the color should be, either between 0 and 1 or 0 and 255. * @return This color */ - public inline function setHSB(Hue:Float, Saturation:Float, Brightness:Float, Alpha:Float):FlxColor + public inline function setHSB(Hue:Float, Saturation:Float, Brightness:Float, Alpha = 1.0):FlxColor { var chroma = Brightness * Saturation; var match = Brightness - chroma; @@ -533,7 +533,7 @@ abstract FlxColor(Int) from Int from UInt to Int to UInt * @param Alpha How opaque the color should be, either between 0 and 1 or 0 and 255 * @return This color */ - public inline function setHSL(Hue:Float, Saturation:Float, Lightness:Float, Alpha:Float):FlxColor + public inline function setHSL(Hue:Float, Saturation:Float, Lightness:Float, Alpha = 1.0):FlxColor { var chroma = (1 - Math.abs(2 * Lightness - 1)) * Saturation; var match = Lightness - chroma / 2; From b3bb9775edea151729911d39bf38ca9ce712f640 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 12:56:06 -0600 Subject: [PATCH 04/14] add various colorTransform helpers --- flixel/util/FlxColorTransformUtil.hx | 115 ++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/flixel/util/FlxColorTransformUtil.hx b/flixel/util/FlxColorTransformUtil.hx index c13f7b4e01..8a50fad847 100644 --- a/flixel/util/FlxColorTransformUtil.hx +++ b/flixel/util/FlxColorTransformUtil.hx @@ -4,7 +4,96 @@ import openfl.geom.ColorTransform; class FlxColorTransformUtil { - public static function setMultipliers(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform + /** + * Resets the transform to default values, multipliers become `1.0` and offsets become `0.0` + */ + public static inline function reset(transform:ColorTransform):ColorTransform + { + return set(transform, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0); + } + + /** + * Quick way to set all of a transform's values + * + * @param rMult The value for the red multiplier, ranges from 0 to 1 + * @param gMult The value for the green multiplier, ranges from 0 to 1 + * @param bMult The value for the blue multiplier, ranges from 0 to 1 + * @param aMult The value for the alpha transparency multiplier, ranges from 0 to 1 + * @param rOffset The offset value for the red color channel, ranges from -255 to 255 + * @param gOffset The offset value for the green color channel, ranges from -255 to 255 + * @param bOffset The offset for the blue color channel value, ranges from -255 to 255 + * @param aOffset The offset for alpha transparency channel value, ranges from -255 to 255 + */ + overload public static inline extern function set(transform:ColorTransform, + rMult, gMult, bMult, aMult = 1.0, + rOffset, gOffset, bOffset, aOffset = 0.0):ColorTransform + { + setMultipliers(transform, rMult, gMult, bMult, aMult); + setOffsets(transform, rOffset, gOffset, bOffset, aOffset); + + return transform; + } + + /** + * Quick way to set all of a transform's values + * + * @param colorMult A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values determine the multipliers of this transform + * @param color A `FlxColor` whos `red`, `green`, `blue` and `alpha` values + * determine the offsets of this transform + */ + overload public static inline extern function set(transform:ColorTransform, colorMult = FlxColor.WHITE, colorOffset:FlxColor = 0x0):ColorTransform + { + return set(transform, + colorMult.redFloat, colorMult.greenFloat, colorMult.blueFloat, colorMult.alphaFloat, + colorOffset.red, colorOffset.green, colorOffset.blue, colorOffset.alpha + ); + } + + /** + * Scales each color's multiplier by the specifified amount + * + * @param rMult The amount to scale the red multiplier + * @param gMult The amount to scale the green multiplier + * @param bMult The amount to scale the blue multiplier + * @param aMult The amount to scale the alpha transparency multiplier + * @return ColorTransform + */ + overload public static inline extern function scaleMultipliers(transform:ColorTransform, rMult = 1.0, gMult = 1.0, bMult = 1.0, aMult = 1.0):ColorTransform + { + transform.redMultiplier *= rMult; + transform.greenMultiplier *= gMult; + transform.blueMultiplier *= bMult; + transform.alphaMultiplier *= aMult; + + return transform; + } + + /** + * Scales each color's multiplier by the color's specifified normal values + * + * @param color A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values scale the multipliers of this transform + */ + overload public static inline extern function scaleMultipliers(transform:ColorTransform, color:FlxColor):ColorTransform + { + transform.redMultiplier *= color.redFloat; + transform.greenMultiplier *= color.greenFloat; + transform.blueMultiplier *= color.blueFloat; + transform.alphaMultiplier *= color.alphaFloat; + + return transform; + } + + /** + * Quick way to set all of a transform's multipliers + * + * @param rMult The value for the red multiplier, ranges from 0 to 1 + * @param gMult The value for the green multiplier, ranges from 0 to 1 + * @param bMult The value for the blue multiplier, ranges from 0 to 1 + * @param aMult The value for the alpha transparency multiplier, ranges from 0 to 1 + */ + overload public static inline extern function setMultipliers(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform { transform.redMultiplier = red; transform.greenMultiplier = green; @@ -13,7 +102,31 @@ class FlxColorTransformUtil return transform; } + + /** + * Quick way to set all of a transform's multipliers with a single color + * + * @param color A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values determine the multipliers of this transform + */ + overload public static inline extern function setMultipliers(transform:ColorTransform, color:FlxColor):ColorTransform + { + transform.redMultiplier = color.redFloat; + transform.greenMultiplier = color.greenFloat; + transform.blueMultiplier = color.blueFloat; + transform.alphaMultiplier = color.alphaFloat; + + return transform; + } + /** + * Quick way to set all of a transform's offsets + * + * @param red The value for the red offset, ranges from 0 to 255 + * @param green The value for the green offset, ranges from 0 to 255 + * @param blue The value for the blue offset, ranges from 0 to 255 + * @param alpha The value for the alpha transparency offset, ranges from 0 to 255 + */ public static function setOffsets(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform { transform.redOffset = red; From 9e25d72b14c8d292b992378e7b800ff0ba99f76d Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 12:56:19 -0600 Subject: [PATCH 05/14] add FlxFrame.overlaps --- flixel/graphics/frames/FlxFrame.hx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/flixel/graphics/frames/FlxFrame.hx b/flixel/graphics/frames/FlxFrame.hx index 63008918de..17d9562a2e 100644 --- a/flixel/graphics/frames/FlxFrame.hx +++ b/flixel/graphics/frames/FlxFrame.hx @@ -584,7 +584,21 @@ class FlxFrame implements IFlxDestroyable copyTo(clippedFrame); return clippedFrame.clip(rect); } - + + /** + * Whether there is any overlap between this frame and the given rect. If clipping this frame to + * the given rect would result in an empty frame, the result is `false` + */ + public function overlaps(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = rect.overlaps(frame); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + /** * Clips this frame to the desired rect * From 5a1ddef396f144da7fe99c0f118f23761cbdf0d9 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 13:11:55 -0600 Subject: [PATCH 06/14] make FlxFrame constructor public --- flixel/graphics/frames/FlxFrame.hx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/flixel/graphics/frames/FlxFrame.hx b/flixel/graphics/frames/FlxFrame.hx index 17d9562a2e..e4038f0b65 100644 --- a/flixel/graphics/frames/FlxFrame.hx +++ b/flixel/graphics/frames/FlxFrame.hx @@ -143,9 +143,7 @@ class FlxFrame implements IFlxDestroyable var blitMatrix:Vector; - @:allow(flixel.graphics.FlxGraphic) - @:allow(flixel.graphics.frames.FlxFramesCollection) - function new(parent:FlxGraphic, angle = FlxFrameAngle.ANGLE_0, flipX = false, flipY = false, duration = 0.0) + public function new(parent:FlxGraphic, angle = FlxFrameAngle.ANGLE_0, flipX = false, flipY = false, duration = 0.0) { this.parent = parent; this.angle = angle; From a9959e316e38809fa61ba29ea7eddee624e1091e Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 14:53:22 -0600 Subject: [PATCH 07/14] add CharList and cleanup text rendering/iteration --- flixel/text/FlxBitmapText.hx | 330 +++++++++++++++-------------------- 1 file changed, 145 insertions(+), 185 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index 4d0ec1551b..fef9cb1a6d 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -1,17 +1,19 @@ package flixel.text; -import openfl.display.BitmapData; import flixel.FlxBasic; import flixel.FlxG; import flixel.FlxSprite; import flixel.graphics.frames.FlxBitmapFont; import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawBaseItem; +import flixel.math.FlxMatrix; import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.text.FlxText.FlxTextAlign; import flixel.text.FlxText.FlxTextBorderStyle; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; +import openfl.display.BitmapData; import openfl.geom.ColorTransform; using flixel.util.FlxColorTransformUtil; @@ -198,9 +200,9 @@ class FlxBitmapText extends FlxSprite var pendingTextBitmapChange:Bool = true; var pendingPixelsChange:Bool = true; - var textData:Array; - var textDrawData:Array; - var borderDrawData:Array; + var textData:CharList; + var textDrawData:CharList; + var borderDrawData:CharList; /** * Helper bitmap buffer for text pixels but without any color transformations @@ -233,7 +235,6 @@ class FlxBitmapText extends FlxSprite else { textData = []; - textDrawData = []; borderDrawData = []; } @@ -313,7 +314,11 @@ class FlxBitmapText extends FlxSprite } } - override public function draw():Void + static final bgColorTransformDrawHelper = new ColorTransform(); + static final borderColorTransformDrawHelper = new ColorTransform(); + static final textColorTransformDrawHelper = new ColorTransform(); + static final matrixDrawHelper = new FlxMatrix(); + override function draw() { if (FlxG.renderBlit) { @@ -323,80 +328,35 @@ class FlxBitmapText extends FlxSprite else { checkPendingChanges(true); - - var textLength:Int = Std.int(textDrawData.length / 3); - var borderLength:Int = Std.int(borderDrawData.length / 3); - - var dataPos:Int; - - var cr:Float = color.redFloat; - var cg:Float = color.greenFloat; - var cb:Float = color.blueFloat; - - var borderRed:Float = borderColor.redFloat * cr; - var borderGreen:Float = borderColor.greenFloat * cg; - var borderBlue:Float = borderColor.blueFloat * cb; - var bAlpha:Float = borderColor.alphaFloat * alpha; - - var textRed:Float = cr; - var textGreen:Float = cg; - var textBlue:Float = cb; - var tAlpha:Float = alpha; - + + final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; + + final textColorTransform = textColorTransformDrawHelper.reset(); + textColorTransform.setMultipliers(colorHelper); if (useTextColor) - { - textRed *= textColor.redFloat; - textGreen *= textColor.greenFloat; - textBlue *= textColor.blueFloat; - tAlpha *= textColor.alphaFloat; - } - - var bgRed:Float = cr; - var bgGreen:Float = cg; - var bgBlue:Float = cb; - var bgAlpha:Float = alpha; - - if (background) - { - bgRed *= backgroundColor.redFloat; - bgGreen *= backgroundColor.greenFloat; - bgBlue *= backgroundColor.blueFloat; - bgAlpha *= backgroundColor.alphaFloat; - } - - var drawItem; - var currFrame:FlxFrame = null; - var currTileX:Float = 0; - var currTileY:Float = 0; - var sx:Float = scale.x * _facingHorizontalMult; - var sy:Float = scale.y * _facingVerticalMult; - - var ox:Float = origin.x; - var oy:Float = origin.y; - - if (_facingHorizontalMult != 1) - { - ox = frameWidth - ox; - } - if (_facingVerticalMult != 1) - { - oy = frameHeight - oy; - } - - var clippedFrameRect; + textColorTransform.scaleMultipliers(textColor); + + final borderColorTransform = borderColorTransformDrawHelper.reset(); + borderColorTransform.setMultipliers(borderColor).scaleMultipliers(colorHelper); + + final scaleX:Float = scale.x * _facingHorizontalMult; + final scaleY:Float = scale.y * _facingVerticalMult; + + final originX:Float = _facingHorizontalMult != 1 ? frameWidth - origin.x : origin.x; + final originY:Float = _facingVerticalMult != 1 ? frameHeight - origin.y : origin.y; + + final clippedFrameRect = FlxRect.get(0, 0, frameWidth, frameHeight); if (clipRect != null) - { - clippedFrameRect = clipRect.intersection(FlxRect.weak(0, 0, frameWidth, frameHeight)); - - if (clippedFrameRect.isEmpty) - return; - } - else - { - clippedFrameRect = FlxRect.get(0, 0, frameWidth, frameHeight); - } + clippedFrameRect.clipTo(clipRect); + if (clippedFrameRect.isEmpty) + return; + + final charClipHelper = FlxRect.get(); + final charClippedFrame = new FlxFrame(null); + final screenPos = FlxPoint.get(); + final cameras = getCamerasLegacy(); for (camera in cameras) { @@ -405,11 +365,11 @@ class FlxBitmapText extends FlxSprite continue; } - getScreenPosition(_point, camera).subtractPoint(offset); + getScreenPosition(screenPos, camera).subtractPoint(offset); if (isPixelPerfectRender(camera)) { - _point.floor(); + screenPos.floor(); } updateTrig(); @@ -417,90 +377,63 @@ class FlxBitmapText extends FlxSprite if (background) { // backround tile transformations - currFrame = FlxG.bitmap.whitePixel; - _matrix.identity(); - _matrix.scale(0.1 * clippedFrameRect.width, 0.1 * clippedFrameRect.height); - _matrix.translate(clippedFrameRect.x - ox, clippedFrameRect.y - oy); - _matrix.scale(sx, sy); + final matrix = matrixDrawHelper; + matrix.identity(); + matrix.scale(0.1 * clippedFrameRect.width, 0.1 * clippedFrameRect.height); + matrix.translate(clippedFrameRect.x - originX, clippedFrameRect.y - originY); + matrix.scale(scaleX, scaleY); if (angle != 0) { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); + matrix.rotateWithTrig(_cosAngle, _sinAngle); } - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(bgRed, bgGreen, bgBlue, bgAlpha); - camera.drawPixels(currFrame, null, _matrix, _colorParams, blend, antialiasing); + matrix.translate(screenPos.x + originX, screenPos.y + originY); + final colorTransform = bgColorTransformDrawHelper.reset(); + colorTransform.setMultipliers(colorHelper).scaleMultipliers(backgroundColor); + camera.drawPixels(FlxG.bitmap.whitePixel, null, matrix, colorTransform, blend, antialiasing); } - - var hasColorOffsets:Bool = (colorTransform != null && colorTransform.hasRGBAOffsets()); - - drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); - - for (j in 0...borderLength) + + final hasColorOffsets = (colorTransform != null && colorTransform.hasRGBAOffsets()); + final drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); + function addQuad(charCode:Int, x:Float, y:Float, color:ColorTransform) { - dataPos = j * 3; - - currFrame = font.getCharFrame(Std.int(borderDrawData[dataPos])); - - currTileX = borderDrawData[dataPos + 1]; - currTileY = borderDrawData[dataPos + 2]; - + final frame = font.getCharFrame(charCode); if (clipRect != null) { - clippedFrameRect.copyFrom(clipRect).offset(-currTileX, -currTileY); - currFrame = currFrame.clipTo(clippedFrameRect); + charClipHelper.copyFrom(clippedFrameRect).offset(-x, -y); + frame.clipTo(charClipHelper, charClippedFrame); } - - currFrame.prepareMatrix(_matrix); - _matrix.translate(currTileX - ox, currTileY - oy); - _matrix.scale(sx, sy); - if (angle != 0) - { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); - } - - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(borderRed, borderGreen, borderBlue, bAlpha); - drawItem.addQuad(currFrame, _matrix, _colorParams); - } - - for (j in 0...textLength) - { - dataPos = j * 3; - - currFrame = font.getCharFrame(Std.int(textDrawData[dataPos])); - - currTileX = textDrawData[dataPos + 1]; - currTileY = textDrawData[dataPos + 2]; - - if (clipRect != null) + else { - clippedFrameRect.copyFrom(clipRect).offset(-currTileX, -currTileY); - currFrame = currFrame.clipTo(clippedFrameRect); + frame.copyTo(charClippedFrame); } - - currFrame.prepareMatrix(_matrix); - _matrix.translate(currTileX - ox, currTileY - oy); - _matrix.scale(sx, sy); + + final matrix = matrixDrawHelper; + charClippedFrame.prepareMatrix(matrix); + matrix.translate(x - originX, y - originY); + matrix.scale(scaleX, scaleY); if (angle != 0) { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); + matrix.rotateWithTrig(_cosAngle, _sinAngle); } - - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(textRed, textGreen, textBlue, tAlpha); - drawItem.addQuad(currFrame, _matrix, _colorParams); + + matrix.translate(screenPos.x + originX, screenPos.y + originY); + drawItem.addQuad(charClippedFrame, matrix, color); } - + + borderDrawData.forEach(addQuad.bind(_, _, _, borderColorTransform)); + textDrawData.forEach(addQuad.bind(_, _, _, textColorTransform)); + #if FLX_DEBUG FlxBasic.visibleCount++; #end } - - // dispose clipRect helpers + + // dispose helpers clippedFrameRect.put(); - + screenPos.put(); + #if FLX_DEBUG if (FlxG.debugger.drawDebug) { @@ -509,7 +442,7 @@ class FlxBitmapText extends FlxSprite #end } } - + override function set_clipRect(Rect:FlxRect):FlxRect { super.set_clipRect(Rect); @@ -1084,7 +1017,7 @@ class FlxBitmapText extends FlxSprite } else if (FlxG.renderTile) { - textData.splice(0, textData.length); + textData.clear(); } _fieldWidth = frameWidth; @@ -1147,19 +1080,15 @@ class FlxBitmapText extends FlxSprite function blitLine(line:UnicodeString, startX:Int, startY:Int):Void { - var data:Array = []; + final data:CharList = []; addLineData(line, startX, startY, data); - while (data.length > 0) + data.forEach(function (charCode, x, y) { - final charCode = Std.int(data.shift()); - final x = data.shift(); - final y = data.shift(); - final charFrame = font.getCharFrame(charCode); _flashPoint.setTo(x, y); charFrame.paint(textBitmap, _flashPoint, true); - } + }); } function tileLine(line:UnicodeString, startX:Int, startY:Int) @@ -1170,10 +1099,8 @@ class FlxBitmapText extends FlxSprite addLineData(line, startX, startY, textData); } - function addLineData(line:UnicodeString, startX:Int, startY:Int, data:Array) + function addLineData(line:UnicodeString, startX:Int, startY:Int, data:CharList) { - var pos:Int = data.length; - var curX:Float = startX; var curY:Int = startY; @@ -1197,11 +1124,7 @@ class FlxBitmapText extends FlxSprite final isSpace = isSpaceChar(charCode); final hasFrame = font.charExists(charCode); if (hasFrame && !isSpace) - { - data[pos++] = charCode; - data[pos++] = curX; - data[pos++] = curY; - } + data.push(charCode, curX, curY); if (hasFrame || isSpace) { @@ -1274,8 +1197,8 @@ class FlxBitmapText extends FlxSprite } else { - textDrawData.splice(0, textDrawData.length); - borderDrawData.splice(0, borderDrawData.length); + textDrawData.clear(); + borderDrawData.clear(); } // use local var to avoid get_width and recursion @@ -1443,39 +1366,29 @@ class FlxBitmapText extends FlxSprite bitmap.draw(textBitmap, _matrix, _colorParams); } } - + function tileText(posX:Int, posY:Int, isFront:Bool = true):Void { if (!FlxG.renderTile) return; - - var data:Array = isFront ? textDrawData : borderDrawData; - - var pos:Int = data.length; - var textPos:Int; - var textLen:Int = Std.int(textData.length / 3); - var rect = FlxRect.get(); - var frameVisible; - - for (i in 0...textLen) + + final data:CharList = isFront ? textDrawData : borderDrawData; + final rect = FlxRect.get(); + + textData.forEach(function (charCode:Int, charX:Float, charY:Float) { - textPos = 3 * i; - - frameVisible = true; - + final charFrame = font.getCharFrame(charCode); + if (clipRect != null) { - rect.copyFrom(clipRect).offset(-textData[textPos + 1] - posX, -textData[textPos + 2] - posY); - frameVisible = font.getCharFrame(Std.int(textData[textPos])).clipTo(rect).type != FlxFrameType.EMPTY; - } - - if (frameVisible) - { - data[pos++] = textData[textPos]; - data[pos++] = textData[textPos + 1] + posX; - data[pos++] = textData[textPos + 2] + posY; + rect.copyFrom(clipRect); + rect.offset(-charX - posX, -charY - posY); + if (!charFrame.overlaps(rect)) + return; } - } + + data.push(charCode, charX + posX, charY + posY); + }); rect.put(); } @@ -1820,6 +1733,53 @@ enum WordSplitConditions WIDTH(minPixels:Int); } +@:forward(length) +abstract CharList(Array) from Array +{ + public inline function new () + { + this = []; + } + + // TODO: deprecate + overload public inline extern function push(item:Float) + { + this.push(item); + } + + overload public inline extern function push(charCode:Int, x:Float, y:Float) + { + this.push(charCode); + this.push(x); + this.push(y); + } + + public function forEach(func:(charCode:Int, x:Float, y:Float)->Void) + { + for (i in 0...Std.int(this.length / 3)) + { + final pos = i * 3; + func(Std.int(this[pos]), this[pos + 1], this[pos + 2]); + } + } + + public inline function clear() + { + this.resize(0); + } + + @:arrayAccess // TODO: deprecate + public inline function get(index:Int):Float + { + return this[index]; + } + @:arrayAccess // TODO: deprecate + public inline function set(index:Int, value:Float):Float + { + return this[index] = value; + } +} + /* * TODO - enum WordSplitMethod: determines how words look when split, ex: * * whether split words start on a new line From df5195822fbfb30188fe015dd2fd43a40ef4289f Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Fri, 7 Mar 2025 15:46:24 -0600 Subject: [PATCH 08/14] add forEachBorder helper --- flixel/text/FlxBitmapText.hx | 100 +++++++++++++++-------------------- 1 file changed, 42 insertions(+), 58 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index fef9cb1a6d..eb27f58bff 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -1213,14 +1213,25 @@ class FlxBitmapText extends FlxSprite bitmap.lock(); } - var isFront:Bool = false; + forEachBorder(drawText.bind(_, _, false, bitmap, useTiles)); + drawText(0, 0, true, bitmap, useTiles); - var iterations:Int = Std.int(borderSize * borderQuality); - iterations = (iterations <= 0) ? 1 : iterations; + if (!useTiles) + { + bitmap.unlock(); + } - var delta:Int = Std.int(borderSize / iterations); + if (FlxG.renderBlit) + { + dirty = true; + } - // render border + if (pendingPixelsChange) + throw "pendingPixelsChange was changed to true while processing changed pixels"; + } + + function forEachBorder(func:(xOffset:Int, yOffset:Int)->Void) + { switch (borderStyle) { case SHADOW if (_shadowOffset.x != 1 || _shadowOffset.y != 1): @@ -1237,7 +1248,7 @@ class FlxBitmapText extends FlxSprite { for (iterX in 0...iterationsX) { - drawText(deltaX * (iterX + 1), deltaY * (iterY + 1), isFront, bitmap, useTiles); + func(deltaX * (iterX + 1), deltaY * (iterY + 1)); } } @@ -1247,7 +1258,7 @@ class FlxBitmapText extends FlxSprite var i = iterations + 1; while (i-- > 1) { - drawText(Std.int(delta * i), Std.int(delta * i), isFront, bitmap, useTiles); + func(Std.int(delta * i), Std.int(delta * i)); } case SHADOW_XY(shadowX, shadowY): @@ -1257,70 +1268,43 @@ class FlxBitmapText extends FlxSprite var i = iterations + 1; while (i-- > 1) { - drawText(Std.int(shadowX / iterations * i), Std.int(shadowY / iterations * i), isFront, bitmap, useTiles); + func(Std.int(shadowX / iterations * i), Std.int(shadowY / iterations * i)); } case OUTLINE: - // Render an outline around the text - // (do 8 offset draw calls) - var itd:Int = 0; + // Render an outline around the text (8 draws) + var iterations:Int = Std.int(borderSize * borderQuality); + iterations = (iterations <= 0) ? 1 : iterations; + final delta = Std.int(borderSize / iterations); for (iter in 0...iterations) { - itd = delta * (iter + 1); - // upper-left - drawText(-itd, -itd, isFront, bitmap, useTiles); - // upper-middle - drawText(0, -itd, isFront, bitmap, useTiles); - // upper-right - drawText(itd, -itd, isFront, bitmap, useTiles); - // middle-left - drawText(-itd, 0, isFront, bitmap, useTiles); - // middle-right - drawText(itd, 0, isFront, bitmap, useTiles); - // lower-left - drawText(-itd, itd, isFront, bitmap, useTiles); - // lower-middle - drawText(0, itd, isFront, bitmap, useTiles); - // lower-right - drawText(itd, itd, isFront, bitmap, useTiles); + final i = delta * (iter + 1); + func(-i, -i); // upper-left + func( 0, -i); // upper-middle + func( i, -i); // upper-right + func(-i, 0); // middle-left + func( i, 0); // middle-right + func(-i, i); // lower-left + func( 0, i); // lower-middle + func( i, i); // lower-right } case OUTLINE_FAST: - // Render an outline around the text - // (do 4 diagonal offset draw calls) - // (this method might not work with certain narrow fonts) - var itd:Int = 0; + // Render an outline around the text in each corner (4 draws) + var iterations:Int = Std.int(borderSize * borderQuality); + iterations = (iterations <= 0) ? 1 : iterations; + final delta = Std.int(borderSize / iterations); for (iter in 0...iterations) { - itd = delta * (iter + 1); - // upper-left - drawText(-itd, -itd, isFront, bitmap, useTiles); - // upper-right - drawText(itd, -itd, isFront, bitmap, useTiles); - // lower-left - drawText(-itd, itd, isFront, bitmap, useTiles); - // lower-right - drawText(itd, itd, isFront, bitmap, useTiles); + final i = delta * (iter + 1); + func(-i, -i); // upper-left + func( i, -i); // upper-right + func(-i, i); // lower-left + func( i, i); // lower-right } case NONE: } - - isFront = true; - drawText(0, 0, isFront, bitmap, useTiles); - - if (!useTiles) - { - bitmap.unlock(); - } - - if (FlxG.renderBlit) - { - dirty = true; - } - - if (pendingPixelsChange) - throw "pendingPixelsChange was changed to true while processing changed pixels"; } - + function drawText(posX:Int, posY:Int, isFront:Bool = true, ?bitmap:BitmapData, useTiles:Bool = false):Void { if (FlxG.renderBlit) From f00bd916963d61ea904be32a6ecdbafee8f14f8e Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Mon, 10 Mar 2025 11:35:32 -0500 Subject: [PATCH 09/14] refactor text tiling, remove char buffer --- flixel/text/FlxBitmapText.hx | 248 +++++++++++++++++++++-------------- 1 file changed, 152 insertions(+), 96 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index eb27f58bff..c8e81d6c38 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -200,9 +200,7 @@ class FlxBitmapText extends FlxSprite var pendingTextBitmapChange:Bool = true; var pendingPixelsChange:Bool = true; - var textData:CharList; - var textDrawData:CharList; - var borderDrawData:CharList; + var lineDrawData:TextLineList; /** * Helper bitmap buffer for text pixels but without any color transformations @@ -234,9 +232,7 @@ class FlxBitmapText extends FlxSprite } else { - textData = []; - textDrawData = []; - borderDrawData = []; + lineDrawData = new TextLineList(this); } this.text = text; @@ -257,12 +253,7 @@ class FlxBitmapText extends FlxSprite _colorParams = null; - if (FlxG.renderTile) - { - textData = null; - textDrawData = null; - borderDrawData = null; - } + lineDrawData = FlxDestroyUtil.destroy(lineDrawData); super.destroy(); } @@ -315,8 +306,6 @@ class FlxBitmapText extends FlxSprite } static final bgColorTransformDrawHelper = new ColorTransform(); - static final borderColorTransformDrawHelper = new ColorTransform(); - static final textColorTransformDrawHelper = new ColorTransform(); static final matrixDrawHelper = new FlxMatrix(); override function draw() { @@ -329,15 +318,11 @@ class FlxBitmapText extends FlxSprite { checkPendingChanges(true); - final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; - - final textColorTransform = textColorTransformDrawHelper.reset(); - textColorTransform.setMultipliers(colorHelper); - if (useTextColor) - textColorTransform.scaleMultipliers(textColor); + final trimmedClipRect = getTrimmedRect(); + if (trimmedClipRect != null && trimmedClipRect.isEmpty) + return; - final borderColorTransform = borderColorTransformDrawHelper.reset(); - borderColorTransform.setMultipliers(borderColor).scaleMultipliers(colorHelper); + final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; final scaleX:Float = scale.x * _facingHorizontalMult; final scaleY:Float = scale.y * _facingVerticalMult; @@ -345,14 +330,6 @@ class FlxBitmapText extends FlxSprite final originX:Float = _facingHorizontalMult != 1 ? frameWidth - origin.x : origin.x; final originY:Float = _facingVerticalMult != 1 ? frameHeight - origin.y : origin.y; - final clippedFrameRect = FlxRect.get(0, 0, frameWidth, frameHeight); - - if (clipRect != null) - clippedFrameRect.clipTo(clipRect); - - if (clippedFrameRect.isEmpty) - return; - final charClipHelper = FlxRect.get(); final charClippedFrame = new FlxFrame(null); final screenPos = FlxPoint.get(); @@ -379,8 +356,8 @@ class FlxBitmapText extends FlxSprite // backround tile transformations final matrix = matrixDrawHelper; matrix.identity(); - matrix.scale(0.1 * clippedFrameRect.width, 0.1 * clippedFrameRect.height); - matrix.translate(clippedFrameRect.x - originX, clippedFrameRect.y - originY); + matrix.scale(0.1 * trimmedClipRect.width, 0.1 * trimmedClipRect.height); + matrix.translate(trimmedClipRect.x - originX, trimmedClipRect.y - originY); matrix.scale(scaleX, scaleY); if (angle != 0) @@ -399,10 +376,13 @@ class FlxBitmapText extends FlxSprite function addQuad(charCode:Int, x:Float, y:Float, color:ColorTransform) { final frame = font.getCharFrame(charCode); - if (clipRect != null) + if (trimmedClipRect != null) { - charClipHelper.copyFrom(clippedFrameRect).offset(-x, -y); + charClipHelper.copyFrom(trimmedClipRect).offset(-x, -y); frame.clipTo(charClipHelper, charClippedFrame); + + if (charClippedFrame.type == EMPTY) + return; } else { @@ -422,8 +402,7 @@ class FlxBitmapText extends FlxSprite drawItem.addQuad(charClippedFrame, matrix, color); } - borderDrawData.forEach(addQuad.bind(_, _, _, borderColorTransform)); - textDrawData.forEach(addQuad.bind(_, _, _, textColorTransform)); + renderTileData(trimmedClipRect, addQuad); #if FLX_DEBUG FlxBasic.visibleCount++; @@ -431,7 +410,7 @@ class FlxBitmapText extends FlxSprite } // dispose helpers - clippedFrameRect.put(); + FlxDestroyUtil.put(trimmedClipRect); screenPos.put(); #if FLX_DEBUG @@ -443,6 +422,55 @@ class FlxBitmapText extends FlxSprite } } + function getTrimmedRect() + { + if (clipRect == null) + return null; + + final result = FlxRect.get(0, 0, frameWidth, frameHeight); + result.clipTo(clipRect); + + return result; + } + + static final borderColorTransformDrawHelper = new ColorTransform(); + static final textColorTransformDrawHelper = new ColorTransform(); + function renderTileData(?frameRect:FlxRect, drawFunc:(char:Int, x:Float, y:Float, color:ColorTransform)->Void) + { + final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; + + final textColorTransform = textColorTransformDrawHelper.reset(); + textColorTransform.setMultipliers(colorHelper); + if (useTextColor) + textColorTransform.scaleMultipliers(textColor); + + final borderColorTransform = borderColorTransformDrawHelper.reset(); + borderColorTransform.setMultipliers(borderColor).scaleMultipliers(colorHelper); + + if (frameRect != null) + { + forEachBorder(function (xOffset, yOffset) + { + lineDrawData.forEachInRect(frameRect, xOffset, yOffset + padding, + drawFunc.bind(_, _, _, borderColorTransform)); + }); + + lineDrawData.forEachInRect(frameRect, 0, padding, drawFunc.bind(_, _, _, textColorTransform)); + } + else + { + final spacing = lineHeight + lineSpacing; + forEachBorder(function (xOffset, yOffset) + { + lineDrawData.forEach(function (char, x, line) + drawFunc(char, x + xOffset, line * spacing + yOffset, borderColorTransform) + ); + }); + + lineDrawData.forEach((char, x, line)->drawFunc(char, x, line * spacing, textColorTransform)); + } + } + override function set_clipRect(Rect:FlxRect):FlxRect { super.set_clipRect(Rect); @@ -1017,7 +1045,7 @@ class FlxBitmapText extends FlxSprite } else if (FlxG.renderTile) { - textData.clear(); + lineDrawData.clear(); } _fieldWidth = frameWidth; @@ -1070,7 +1098,7 @@ class FlxBitmapText extends FlxSprite if (useTiles) { - tileLine(line, posX, posY); + tileLine(line, posX); } else { @@ -1081,28 +1109,29 @@ class FlxBitmapText extends FlxSprite function blitLine(line:UnicodeString, startX:Int, startY:Int):Void { final data:CharList = []; - addLineData(line, startX, startY, data); + addLineData(line, startX, data); - data.forEach(function (charCode, x, y) + data.forEach(function (charCode, x) { final charFrame = font.getCharFrame(charCode); - _flashPoint.setTo(x, y); + _flashPoint.setTo(x, startY); charFrame.paint(textBitmap, _flashPoint, true); }); } - function tileLine(line:UnicodeString, startX:Int, startY:Int) + function tileLine(line:UnicodeString, startX:Int) { if (!FlxG.renderTile) return; - addLineData(line, startX, startY, textData); + final lineData:CharList = []; + addLineData(line, startX, lineData); + lineDrawData.push(lineData); } - function addLineData(line:UnicodeString, startX:Int, startY:Int, data:CharList) + function addLineData(line:UnicodeString, startX:Int, data:CharList) { var curX:Float = startX; - var curY:Int = startY; final lineLength:Int = line.length; final textWidth:Int = this.textWidth; @@ -1124,7 +1153,7 @@ class FlxBitmapText extends FlxSprite final isSpace = isSpaceChar(charCode); final hasFrame = font.charExists(charCode); if (hasFrame && !isSpace) - data.push(charCode, curX, curY); + data.push(charCode, curX); if (hasFrame || isSpace) { @@ -1197,8 +1226,7 @@ class FlxBitmapText extends FlxSprite } else { - textDrawData.clear(); - borderDrawData.clear(); + return; } // use local var to avoid get_width and recursion @@ -1314,7 +1342,6 @@ class FlxBitmapText extends FlxSprite if (useTiles) { - tileText(posX, posY, isFront); } else { @@ -1350,32 +1377,6 @@ class FlxBitmapText extends FlxSprite bitmap.draw(textBitmap, _matrix, _colorParams); } } - - function tileText(posX:Int, posY:Int, isFront:Bool = true):Void - { - if (!FlxG.renderTile) - return; - - final data:CharList = isFront ? textDrawData : borderDrawData; - final rect = FlxRect.get(); - - textData.forEach(function (charCode:Int, charX:Float, charY:Float) - { - final charFrame = font.getCharFrame(charCode); - - if (clipRect != null) - { - rect.copyFrom(clipRect); - rect.offset(-charX - posX, -charY - posY); - if (!charFrame.overlaps(rect)) - return; - } - - data.push(charCode, charX + posX, charY + posY); - }); - - rect.put(); - } /** * Set border's style (shadow, outline, etc), color, and size all in one go! @@ -1717,50 +1718,105 @@ enum WordSplitConditions WIDTH(minPixels:Int); } + +class TextLineList implements IFlxDestroyable +{ + public final lines:Array = []; + + var target:FlxBitmapText; + + public function new (target:FlxBitmapText) { this.target = target; } + + public function destroy () + { + clear(); + target = null; + } + + public inline function clear() + { + lines.resize(0); + } + + public function push(line:CharList) + { + lines.push(line); + } + + public function forEach(func:(charCode:Int, x:Float, line:Int)->Void) + { + for (i=>line in lines) + line.forEach(func.bind(_, _, i)); + } + + public function forEachInRect(rect:FlxRect, xOffset:Float, yOffset:Float, func:(charCode:Int, x:Float, y:Float)->Void) + { + function clamp(value:Int) + { + return value < 0 ? 0 : value > lines.length ? lines.length : value; + } + + final start = clamp(Math.floor((rect.top - yOffset) / (target.lineHeight + target.lineSpacing))); + final end = clamp(Math.ceil((rect.bottom - yOffset) / (target.lineHeight + target.lineSpacing))); + for (i in start...end) + { + final y = i * (target.lineHeight + target.lineSpacing) + yOffset; + // if (y > rect.bottom || y + target.lineHeight < rect.y) + // continue; + + // TODO: get left and right + lines[i].forEachInBounds(target, rect.left, rect.right, (charCode, x)->func(charCode, x + xOffset, y)); + } + } +} + @:forward(length) abstract CharList(Array) from Array { + static inline var LENGTH = 2; + static inline var CHAR = 0; + static inline var X = 1; public inline function new () { this = []; } - // TODO: deprecate - overload public inline extern function push(item:Float) - { - this.push(item); - } - - overload public inline extern function push(charCode:Int, x:Float, y:Float) + overload public inline extern function push(charCode:Int, x:Float) { this.push(charCode); this.push(x); - this.push(y); } - public function forEach(func:(charCode:Int, x:Float, y:Float)->Void) + public function forEach(func:(charCode:Int, x:Float)->Void) { - for (i in 0...Std.int(this.length / 3)) + for (i in 0...Std.int(this.length / LENGTH)) { - final pos = i * 3; - func(Std.int(this[pos]), this[pos + 1], this[pos + 2]); + final pos = i * LENGTH; + func(Std.int(this[pos]), this[pos + 1]); } } - public inline function clear() + public function forEachInBounds(target:FlxBitmapText, left:Float, right:Float, func:(charCode:Int, x:Float)->Void) { - this.resize(0); + for (i in 0...Std.int(this.length / LENGTH)) + { + final pos = i * LENGTH; + final char = Std.int(this[pos + CHAR]); + final frame = target.font.getCharFrame(char); + final x = this[pos + X]; + if (x + frame.frame.width >= left && x < right) + func(char, x); + } } - @:arrayAccess // TODO: deprecate - public inline function get(index:Int):Float + public function getChar(index:Int):Int { - return this[index]; + return Std.int(this[index * LENGTH + CHAR]); } - @:arrayAccess // TODO: deprecate - public inline function set(index:Int, value:Float):Float + + public function getX(index:Int):Float { - return this[index] = value; + return this[index * LENGTH + X]; } } From 657bd5596959a70ba9fe934f360f2a4f620f387b Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Tue, 11 Mar 2025 09:51:05 -0500 Subject: [PATCH 10/14] add contains and isContained --- flixel/graphics/frames/FlxFrame.hx | 31 ++++++++++++++++++++++++++++++ flixel/math/FlxRect.hx | 17 ++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/flixel/graphics/frames/FlxFrame.hx b/flixel/graphics/frames/FlxFrame.hx index e4038f0b65..ec4bd00934 100644 --- a/flixel/graphics/frames/FlxFrame.hx +++ b/flixel/graphics/frames/FlxFrame.hx @@ -597,6 +597,37 @@ class FlxFrame implements IFlxDestroyable return result; } + + /** + * Whether this frame fully contains the given rect. If clipping this frame to + * the given rect would result in a smaller frame, the result is `false` + * @since 6.1.0 + */ + public function contains(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = frame.contains(rect); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + + /** + * Whether this frame is fully contained by the given rect. If clipping this frame to + * the given rect would result in a smaller frame, the result is `false` + * @since 6.1.0 + */ + public function isContained(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = rect.contains(frame); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + /** * Clips this frame to the desired rect * diff --git a/flixel/math/FlxRect.hx b/flixel/math/FlxRect.hx index cb7715db0e..8c973a377f 100644 --- a/flixel/math/FlxRect.hx +++ b/flixel/math/FlxRect.hx @@ -285,6 +285,23 @@ class FlxRect implements IFlxPooled return result; } + /** + * Checks to see if this rectangle fully contains another + * + * @param rect The other rectangle + * @return Whether this rectangle contains the given rectangle + * @since 6.1.0 + */ + public inline function contains(rect:FlxRect):Bool + { + final result = rect.left >= left + && rect.right <= right + && rect.top >= top + && rect.bottom <= bottom; + rect.putWeak(); + return result; + } + /** * Returns true if this FlxRect contains the FlxPoint * From b357e9ef58d8e1b9dec810685942bfe66d60f531 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Tue, 11 Mar 2025 10:18:31 -0500 Subject: [PATCH 11/14] improve perf by reducing frame clipTo calls --- flixel/text/FlxBitmapText.hx | 140 +++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 40 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index c8e81d6c38..2e00fbeec5 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -330,8 +330,6 @@ class FlxBitmapText extends FlxSprite final originX:Float = _facingHorizontalMult != 1 ? frameWidth - origin.x : origin.x; final originY:Float = _facingVerticalMult != 1 ? frameHeight - origin.y : origin.y; - final charClipHelper = FlxRect.get(); - final charClippedFrame = new FlxFrame(null); final screenPos = FlxPoint.get(); final cameras = getCamerasLegacy(); @@ -354,9 +352,10 @@ class FlxBitmapText extends FlxSprite if (background) { // backround tile transformations + final frame = FlxG.bitmap.whitePixel; final matrix = matrixDrawHelper; matrix.identity(); - matrix.scale(0.1 * trimmedClipRect.width, 0.1 * trimmedClipRect.height); + matrix.scale(trimmedClipRect.width / frame.frame.width, 0.1 * trimmedClipRect.height / frame.frame.height); matrix.translate(trimmedClipRect.x - originX, trimmedClipRect.y - originY); matrix.scale(scaleX, scaleY); @@ -373,24 +372,10 @@ class FlxBitmapText extends FlxSprite final hasColorOffsets = (colorTransform != null && colorTransform.hasRGBAOffsets()); final drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); - function addQuad(charCode:Int, x:Float, y:Float, color:ColorTransform) + function addQuad(frame:FlxFrame, x:Float, y:Float, color:ColorTransform) { - final frame = font.getCharFrame(charCode); - if (trimmedClipRect != null) - { - charClipHelper.copyFrom(trimmedClipRect).offset(-x, -y); - frame.clipTo(charClipHelper, charClippedFrame); - - if (charClippedFrame.type == EMPTY) - return; - } - else - { - frame.copyTo(charClippedFrame); - } - final matrix = matrixDrawHelper; - charClippedFrame.prepareMatrix(matrix); + frame.prepareMatrix(matrix); matrix.translate(x - originX, y - originY); matrix.scale(scaleX, scaleY); if (angle != 0) @@ -399,7 +384,7 @@ class FlxBitmapText extends FlxSprite } matrix.translate(screenPos.x + originX, screenPos.y + originY); - drawItem.addQuad(charClippedFrame, matrix, color); + drawItem.addQuad(frame, matrix, color); } renderTileData(trimmedClipRect, addQuad); @@ -435,7 +420,16 @@ class FlxBitmapText extends FlxSprite static final borderColorTransformDrawHelper = new ColorTransform(); static final textColorTransformDrawHelper = new ColorTransform(); - function renderTileData(?frameRect:FlxRect, drawFunc:(char:Int, x:Float, y:Float, color:ColorTransform)->Void) + /** + * Finds every character in the given rect, starting with their borders calls drawFunc on + * every character, before rawing foreground text on top. Any character not fully contained + * by the rect is trimmed. If no frameRect is provided, all characters are fully drawn. + * + * **Note:** For performance reasons (and aesthetic preference) the border is trimmed so + * that it extends beyond it's foreground counterpart. I.E.: a border size of 1 will extend + * 1px beyond the trim rect. + */ + function renderTileData(?frameRect:FlxRect, drawFunc:(frame:FlxFrame, x:Float, y:Float, color:ColorTransform)->Void) { final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; @@ -449,13 +443,15 @@ class FlxBitmapText extends FlxSprite if (frameRect != null) { - forEachBorder(function (xOffset, yOffset) + lineDrawData.forEachTrimmed(frameRect, padding, padding, function (frame, x, y) { - lineDrawData.forEachInRect(frameRect, xOffset, yOffset + padding, - drawFunc.bind(_, _, _, borderColorTransform)); + forEachBorder(function (xOffset, yOffset) + { + drawFunc(frame, x + xOffset, y + yOffset, borderColorTransform); + }); }); - lineDrawData.forEachInRect(frameRect, 0, padding, drawFunc.bind(_, _, _, textColorTransform)); + lineDrawData.forEachTrimmed(frameRect, padding, padding, drawFunc.bind(_, _, _, textColorTransform)); } else { @@ -463,11 +459,11 @@ class FlxBitmapText extends FlxSprite forEachBorder(function (xOffset, yOffset) { lineDrawData.forEach(function (char, x, line) - drawFunc(char, x + xOffset, line * spacing + yOffset, borderColorTransform) + drawFunc(font.getCharFrame(char), x + xOffset, line * spacing + yOffset, borderColorTransform) ); }); - lineDrawData.forEach((char, x, line)->drawFunc(char, x, line * spacing, textColorTransform)); + lineDrawData.forEach((char, x, line)->drawFunc(font.getCharFrame(char), x, line * spacing, textColorTransform)); } } @@ -1718,9 +1714,13 @@ enum WordSplitConditions WIDTH(minPixels:Int); } - +/** + * Used internally to track and iterate character rendering positions of multiple lines of text + */ class TextLineList implements IFlxDestroyable { + static final trimmedRectHelper = FlxRect.get(); + public final lines:Array = []; var target:FlxBitmapText; @@ -1733,60 +1733,108 @@ class TextLineList implements IFlxDestroyable target = null; } + /** + * removes all lines of text + */ public inline function clear() { lines.resize(0); } + /** + * Adds a line of text + */ public function push(line:CharList) { lines.push(line); } + /** + * Loops through each line of each character and calls `func` on every character + */ public function forEach(func:(charCode:Int, x:Float, line:Int)->Void) { for (i=>line in lines) line.forEach(func.bind(_, _, i)); } - public function forEachInRect(rect:FlxRect, xOffset:Float, yOffset:Float, func:(charCode:Int, x:Float, y:Float)->Void) + /** + * Loops through each line of each character and calls `func` on every character touching the + * given rectangle. Unlike `forEach` this returns the character's frame, which is trimmed if + * the frame lies in the edge of the trimming rectangle + * + * @param rect The trimming rectangle + * @param xOffset Any offset to apply to each char before checking against the rect + * @param yOffset Any offset to apply to each char before checking against the rect + * @param func The operation to perform on each character inside the rect + * @return ->Void) + */ + public function forEachTrimmed(rect:FlxRect, xOffset:Float, yOffset:Float, func:(frame:FlxFrame, x:Float, y:Float)->Void) { function clamp(value:Int) { return value < 0 ? 0 : value > lines.length ? lines.length : value; } + final trimmedFrame = new FlxFrame(null); + final start = clamp(Math.floor((rect.top - yOffset) / (target.lineHeight + target.lineSpacing))); final end = clamp(Math.ceil((rect.bottom - yOffset) / (target.lineHeight + target.lineSpacing))); for (i in start...end) { final y = i * (target.lineHeight + target.lineSpacing) + yOffset; - // if (y > rect.bottom || y + target.lineHeight < rect.y) - // continue; - // TODO: get left and right - lines[i].forEachInBounds(target, rect.left, rect.right, (charCode, x)->func(charCode, x + xOffset, y)); + lines[i].forEachInBounds(target.font, rect.left - xOffset, rect.right - xOffset, function (charCode, x) + { + final frame = target.font.getCharFrame(charCode); + trimmedRectHelper.copyFrom(rect).offset(-x - xOffset, -y - yOffset); + if (frame.isContained(trimmedRectHelper)) + { + // no trimming necessary + func(frame, x + xOffset, y); + } + else if (frame.overlaps(trimmedRectHelper)) + { + // copy the frame, trim it and draw + frame.clipTo(trimmedRectHelper, trimmedFrame); + func(trimmedFrame, x + xOffset, y); + } + }); } + + trimmedFrame.destroy(); } } +/** + * Used internally to track and iterate character rendering positions of a line of text + */ @:forward(length) abstract CharList(Array) from Array { static inline var LENGTH = 2; static inline var CHAR = 0; static inline var X = 1; + public inline function new () { this = []; } + /** + * Adds a character + * @param charCode An int code representing the character + * @param x The x of the character relative to the `FlxBitmapText` + */ overload public inline extern function push(charCode:Int, x:Float) { this.push(charCode); this.push(x); } + /** + * Loops through and calls `func` on every character + */ public function forEach(func:(charCode:Int, x:Float)->Void) { for (i in 0...Std.int(this.length / LENGTH)) @@ -1796,25 +1844,37 @@ abstract CharList(Array) from Array } } - public function forEachInBounds(target:FlxBitmapText, left:Float, right:Float, func:(charCode:Int, x:Float)->Void) + /** + * Loops through and calls `func` on every character touching the given bounds + * + * @param font The `FlxBitmapFont`, used to get frame data + * @param left The left-most clipping bounds + * @param right The right-most clipping bounds + */ + public function forEachInBounds(font:FlxBitmapFont, left:Float, right:Float, func:(charCode:Int, x:Float)->Void) { for (i in 0...Std.int(this.length / LENGTH)) { - final pos = i * LENGTH; - final char = Std.int(this[pos + CHAR]); - final frame = target.font.getCharFrame(char); - final x = this[pos + X]; + final char = getChar(i); + final frame = font.getCharFrame(char); + final x = getX(i); if (x + frame.frame.width >= left && x < right) func(char, x); } } - public function getChar(index:Int):Int + /** + * Returns the character at the given index + */ + public inline function getChar(index:Int):Int { return Std.int(this[index * LENGTH + CHAR]); } - public function getX(index:Int):Float + /** + * Returns the c position at the given index + */ + public inline function getX(index:Int):Float { return this[index * LENGTH + X]; } From 23cc323832b1462ee58654a74dfc61916eacd730 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Tue, 11 Mar 2025 10:29:27 -0500 Subject: [PATCH 12/14] check padding --- flixel/text/FlxBitmapText.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index 2e00fbeec5..a2d940422b 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -459,11 +459,11 @@ class FlxBitmapText extends FlxSprite forEachBorder(function (xOffset, yOffset) { lineDrawData.forEach(function (char, x, line) - drawFunc(font.getCharFrame(char), x + xOffset, line * spacing + yOffset, borderColorTransform) + drawFunc(font.getCharFrame(char), x + xOffset + padding, line * spacing + yOffset + padding, borderColorTransform) ); }); - lineDrawData.forEach((char, x, line)->drawFunc(font.getCharFrame(char), x, line * spacing, textColorTransform)); + lineDrawData.forEach((char, x, line)->drawFunc(font.getCharFrame(char), x + padding, line * spacing + padding, textColorTransform)); } } From aa0e40749a382079845e13d834d6eb2652b195d6 Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Tue, 11 Mar 2025 12:43:11 -0500 Subject: [PATCH 13/14] add renderTileCharFrame --- flixel/text/FlxBitmapText.hx | 167 ++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 23 deletions(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index a2d940422b..4093801658 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -355,8 +355,18 @@ class FlxBitmapText extends FlxSprite final frame = FlxG.bitmap.whitePixel; final matrix = matrixDrawHelper; matrix.identity(); - matrix.scale(trimmedClipRect.width / frame.frame.width, 0.1 * trimmedClipRect.height / frame.frame.height); - matrix.translate(trimmedClipRect.x - originX, trimmedClipRect.y - originY); + if (trimmedClipRect != null) + { + matrix.scale(trimmedClipRect.width, trimmedClipRect.height); + matrix.translate(trimmedClipRect.x, trimmedClipRect.y); + } + else + { + matrix.scale(frameWidth, frameHeight); + } + + matrix.scale(1 / frame.frame.width, 1 / frame.frame.height); + matrix.translate(-originX, -originY); matrix.scale(scaleX, scaleY); if (angle != 0) @@ -372,7 +382,7 @@ class FlxBitmapText extends FlxSprite final hasColorOffsets = (colorTransform != null && colorTransform.hasRGBAOffsets()); final drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); - function addQuad(frame:FlxFrame, x:Float, y:Float, color:ColorTransform) + function addQuad(frame:FlxFrame, x:Float, y:Float, color:ColorTransform, charIndex:Int, lineIndex:Int, isBorder:Bool) { final matrix = matrixDrawHelper; frame.prepareMatrix(matrix); @@ -384,10 +394,10 @@ class FlxBitmapText extends FlxSprite } matrix.translate(screenPos.x + originX, screenPos.y + originY); - drawItem.addQuad(frame, matrix, color); + renderTileCharFrame(drawItem, frame, matrix, color, charIndex, lineIndex, isBorder); } - renderTileData(trimmedClipRect, addQuad); + renderTileData(trimmedClipRect, padding, padding, addQuad); #if FLX_DEBUG FlxBasic.visibleCount++; @@ -428,8 +438,11 @@ class FlxBitmapText extends FlxSprite * **Note:** For performance reasons (and aesthetic preference) the border is trimmed so * that it extends beyond it's foreground counterpart. I.E.: a border size of 1 will extend * 1px beyond the trim rect. + * + * @param xOffset Offsets the text position, used for scrolling or padding + * @param yOffset Offsets the text position, used for scrolling or padding */ - function renderTileData(?frameRect:FlxRect, drawFunc:(frame:FlxFrame, x:Float, y:Float, color:ColorTransform)->Void) + function renderTileData(?frameRect:FlxRect, xOffset:Float, yOffset:Float, drawFunc:CharDrawFunction) { final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; @@ -443,30 +456,50 @@ class FlxBitmapText extends FlxSprite if (frameRect != null) { - lineDrawData.forEachTrimmed(frameRect, padding, padding, function (frame, x, y) + lineDrawData.forEachTrimmedI(frameRect, xOffset, yOffset, function (frame, x, y, charIndex, lineIndex) { - forEachBorder(function (xOffset, yOffset) + forEachBorder(function (xCharOffset, yCharOffset) { - drawFunc(frame, x + xOffset, y + yOffset, borderColorTransform); + drawFunc(frame, x + xCharOffset, y + yCharOffset, borderColorTransform, charIndex, lineIndex, true); }); }); - lineDrawData.forEachTrimmed(frameRect, padding, padding, drawFunc.bind(_, _, _, textColorTransform)); + lineDrawData.forEachTrimmedI(frameRect, xOffset, yOffset, drawFunc.bind(_, _, _, textColorTransform, _, _, false)); } else { final spacing = lineHeight + lineSpacing; - forEachBorder(function (xOffset, yOffset) + forEachBorder(function (xCharOffset, yCharOffset) { - lineDrawData.forEach(function (char, x, line) - drawFunc(font.getCharFrame(char), x + xOffset + padding, line * spacing + yOffset + padding, borderColorTransform) + lineDrawData.forEachI(function (char, x, charIndex, lineIndex) + drawFunc(font.getCharFrame(char), x + xCharOffset, lineIndex * spacing + yCharOffset, + borderColorTransform, charIndex, lineIndex, true) ); }); - lineDrawData.forEach((char, x, line)->drawFunc(font.getCharFrame(char), x + padding, line * spacing + padding, textColorTransform)); + lineDrawData.forEachI(function (char, x, charIndex, lineIndex) + { + drawFunc(font.getCharFrame(char), x + xOffset, lineIndex * spacing + yOffset, textColorTransform, charIndex, lineIndex, false); + }); } } + /** + * Used internally to render each character in renderTile mode. Can be overriden to + * create per-character effects, like shakey text, or multiple formats + * + * @param drawItem The triangle or quad batch that will render this character + * @param frame The character's frame + * @param matrix the transformation matrix determining the screen position of the character + * @param color The color transformation to apply to this character (`Note:` this instance is used on multiple chars ) + * @param charIndex + * @param lineIndex + */ + function renderTileCharFrame(drawItem:FlxDrawBaseItem, frame, matrix, color, charIndex:Int, lineIndex:Int, isBorder:Bool) + { + drawItem.addQuad(frame, matrix, color); + } + override function set_clipRect(Rect:FlxRect):FlxRect { super.set_clipRect(Rect); @@ -1714,6 +1747,8 @@ enum WordSplitConditions WIDTH(minPixels:Int); } +typedef CharDrawFunction = (frame:FlxFrame, x:Float, y:Float, color:ColorTransform, charIndex:Int, lineIndex:Int, isBorder:Bool)->Void; + /** * Used internally to track and iterate character rendering positions of multiple lines of text */ @@ -1758,15 +1793,25 @@ class TextLineList implements IFlxDestroyable line.forEach(func.bind(_, _, i)); } + /** + * Loops through each line of each character and calls `func` on every character. + * Similar to `forEach` but includes the character's index on it's line as well + */ + public function forEachI(func:(charCode:Int, x:Float, index:Int, line:Int)->Void) + { + for (i=>line in lines) + line.forEachI(func.bind(_, _, _, i)); + } + /** * Loops through each line of each character and calls `func` on every character touching the * given rectangle. Unlike `forEach` this returns the character's frame, which is trimmed if * the frame lies in the edge of the trimming rectangle * - * @param rect The trimming rectangle - * @param xOffset Any offset to apply to each char before checking against the rect - * @param yOffset Any offset to apply to each char before checking against the rect - * @param func The operation to perform on each character inside the rect + * @param rect The trimming rectangle + * @param xOffset Any offset to apply to each char before checking against the rect + * @param yOffset Any offset to apply to each char before checking against the rect + * @param func The operation to perform on each character inside the rect * @return ->Void) */ public function forEachTrimmed(rect:FlxRect, xOffset:Float, yOffset:Float, func:(frame:FlxFrame, x:Float, y:Float)->Void) @@ -1804,6 +1849,53 @@ class TextLineList implements IFlxDestroyable trimmedFrame.destroy(); } + + /** + * Loops through each line of each character and calls `func` on every character touching the + * given rectangle. Unlike `forEach` this returns the character's frame, which is trimmed if + * the frame lies in the edge of the trimming rectangle + * + * @param rect The trimming rectangle + * @param xOffset Any offset to apply to each char before checking against the rect + * @param yOffset Any offset to apply to each char before checking against the rect + * @param func The operation to perform on each character inside the rect + * @return ->Void) + */ + public function forEachTrimmedI(rect:FlxRect, xOffset:Float, yOffset:Float, func:(frame:FlxFrame, x:Float, y:Float, charIndex:Int, lineIndex:Int)->Void) + { + function clamp(value:Int) + { + return value < 0 ? 0 : value > lines.length ? lines.length : value; + } + + final trimmedFrame = new FlxFrame(null); + + final start = clamp(Math.floor((rect.top - yOffset) / (target.lineHeight + target.lineSpacing))); + final end = clamp(Math.ceil((rect.bottom - yOffset) / (target.lineHeight + target.lineSpacing))); + for (lineIndex in start...end) + { + final y = lineIndex * (target.lineHeight + target.lineSpacing) + yOffset; + + lines[lineIndex].forEachInBoundsI(target.font, rect.left - xOffset, rect.right - xOffset, function (charCode, x, charIndex) + { + final frame = target.font.getCharFrame(charCode); + trimmedRectHelper.copyFrom(rect).offset(-x - xOffset, -y); + if (frame.isContained(trimmedRectHelper)) + { + // no trimming necessary + func(frame, x + xOffset, y, charIndex, lineIndex); + } + else if (frame.overlaps(trimmedRectHelper)) + { + // copy the frame, trim it and draw + frame.clipTo(trimmedRectHelper, trimmedFrame); + func(trimmedFrame, x + xOffset, y, charIndex, lineIndex); + } + }); + } + + trimmedFrame.destroy(); + } } /** @@ -1823,8 +1915,8 @@ abstract CharList(Array) from Array /** * Adds a character - * @param charCode An int code representing the character - * @param x The x of the character relative to the `FlxBitmapText` + * @param charCode An int code representing the character + * @param x The x of the character relative to the `FlxBitmapText` */ overload public inline extern function push(charCode:Int, x:Float) { @@ -1844,12 +1936,22 @@ abstract CharList(Array) from Array } } + /** + * Loops through and calls `func` on every character, similar to `forEach` but includes the + * character's index on it's line as well + */ + public function forEachI(func:(charCode:Int, x:Float, index:Int)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + func(getChar(i), getX(i), i); + } + /** * Loops through and calls `func` on every character touching the given bounds * - * @param font The `FlxBitmapFont`, used to get frame data - * @param left The left-most clipping bounds - * @param right The right-most clipping bounds + * @param font The `FlxBitmapFont`, used to get frame data + * @param left The left-most clipping bounds + * @param right The right-most clipping bounds */ public function forEachInBounds(font:FlxBitmapFont, left:Float, right:Float, func:(charCode:Int, x:Float)->Void) { @@ -1863,6 +1965,25 @@ abstract CharList(Array) from Array } } + /** + * Loops through and calls `func` on every character touching the given bounds + * + * @param font The `FlxBitmapFont`, used to get frame data + * @param left The left-most clipping bounds + * @param right The right-most clipping bounds + */ + public function forEachInBoundsI(font:FlxBitmapFont, left:Float, right:Float, func:(charCode:Int, x:Float, index:Int)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + { + final char = getChar(i); + final frame = font.getCharFrame(char); + final x = getX(i); + if (x + frame.frame.width >= left && x < right) + func(char, x, i); + } + } + /** * Returns the character at the given index */ From 1b651bb96c19340995d3fd2d4b67c4d23995d22a Mon Sep 17 00:00:00 2001 From: GeoKureli-BlackbookPro Date: Tue, 11 Mar 2025 12:53:17 -0500 Subject: [PATCH 14/14] fix offset --- flixel/text/FlxBitmapText.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index 4093801658..c334a9fd23 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -472,7 +472,7 @@ class FlxBitmapText extends FlxSprite forEachBorder(function (xCharOffset, yCharOffset) { lineDrawData.forEachI(function (char, x, charIndex, lineIndex) - drawFunc(font.getCharFrame(char), x + xCharOffset, lineIndex * spacing + yCharOffset, + drawFunc(font.getCharFrame(char), x + xOffset + xCharOffset, lineIndex * spacing + yOffset + yCharOffset, borderColorTransform, charIndex, lineIndex, true) ); });