From eb0bf80dfa330e58b778ccdbf03dd030c67245ff Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 29 Apr 2026 15:44:17 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Fix=20the=20avatar-editor=20face?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/UITexts.example | 3 +- .../avatar/AvatarEditorThumbnailsHelper.ts | 48 ++++++++++++++++++- .../avatar-editor/AvatarEditorView.tsx | 2 +- .../AvatarEditorFigureSetItemView.tsx | 2 +- .../figure-set/AvatarEditorFigureSetView.tsx | 4 +- .../views/friends-bar/FriendBarItemView.tsx | 8 ++-- .../views/friends-bar/FriendsBarView.tsx | 4 +- src/css/index.css | 2 +- src/layout/InfiniteGrid.tsx | 47 ++++++++++++++++-- 9 files changed, 102 insertions(+), 18 deletions(-) diff --git a/public/UITexts.example b/public/UITexts.example index c7480c7..34719b4 100644 --- a/public/UITexts.example +++ b/public/UITexts.example @@ -168,6 +168,7 @@ "nitro.login.error.missing_username": "Please choose a Habbo name.", "nitro.login.error.username_length": "Habbo name must be 3–16 characters.", "nitro.login.error.username_taken": "This Habbo name is already taken.", - "nitro.login.error.missing_email": "Please enter your email address." + "nitro.login.error.missing_email": "Please enter your email address.", + "inventory.effects.activate": "Use selected effect" } } diff --git a/src/api/avatar/AvatarEditorThumbnailsHelper.ts b/src/api/avatar/AvatarEditorThumbnailsHelper.ts index c319d2a..9cb58b6 100644 --- a/src/api/avatar/AvatarEditorThumbnailsHelper.ts +++ b/src/api/avatar/AvatarEditorThumbnailsHelper.ts @@ -227,9 +227,11 @@ export class AvatarEditorThumbnailsHelper if(isDisabled) sprite.filters = [ AvatarEditorThumbnailsHelper.ALPHA_FILTER ]; + const frame = AvatarEditorThumbnailsHelper.findOpaqueBoundsFrame(sprite, texture.width, texture.height); + const imageUrl = await TextureUtils.generateImageUrl({ target: sprite, - frame: new NitroRectangle(0, 0, texture.width, texture.height) + frame }); sprite.destroy(); @@ -244,6 +246,50 @@ export class AvatarEditorThumbnailsHelper }); } + private static findOpaqueBoundsFrame(sprite: NitroSprite, fallbackWidth: number, fallbackHeight: number): NitroRectangle + { + try + { + const data = TextureUtils.getPixels(sprite); + if(!data) return new NitroRectangle(0, 0, fallbackWidth, fallbackHeight); + + const pixels = data.pixels as Uint8ClampedArray | Uint8Array; + const width = data.width; + const height = data.height; + if(!pixels || width <= 0 || height <= 0) return new NitroRectangle(0, 0, fallbackWidth, fallbackHeight); + + const ALPHA_THRESHOLD = 8; + + let minX = width; + let minY = height; + let maxX = -1; + let maxY = -1; + + for(let y = 0; y < height; y++) + { + const rowStart = y * width * 4; + for(let x = 0; x < width; x++) + { + if(pixels[rowStart + (x * 4) + 3] > ALPHA_THRESHOLD) + { + if(x < minX) minX = x; + if(x > maxX) maxX = x; + if(y < minY) minY = y; + if(y > maxY) maxY = y; + } + } + } + + if(maxX < minX || maxY < minY) return new NitroRectangle(0, 0, fallbackWidth, fallbackHeight); + + return new NitroRectangle(minX, minY, (maxX - minX) + 1, (maxY - minY) + 1); + } + catch + { + return new NitroRectangle(0, 0, fallbackWidth, fallbackHeight); + } + } + private static sortByDrawOrder(a: IFigurePart, b: IFigurePart): number { const indexA = AvatarEditorThumbnailsHelper.DRAW_ORDER.indexOf(a.type); diff --git a/src/components/avatar-editor/AvatarEditorView.tsx b/src/components/avatar-editor/AvatarEditorView.tsx index a150bec..2270a7b 100644 --- a/src/components/avatar-editor/AvatarEditorView.tsx +++ b/src/components/avatar-editor/AvatarEditorView.tsx @@ -78,7 +78,7 @@ export const AvatarEditorView: FC<{}> = props => return ( setIsVisible(false) } /> diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx index 31c88a6..08c7c67 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetItemView.tsx @@ -77,7 +77,7 @@ export const AvatarEditorFigureSetItemView: FC<{ itemActive={ isSelected } itemImage={ (!partItem.isClear && isHead) ? assetUrl : undefined } className={ `avatar-parts mx-auto${ isSelected ? ' part-selected' : '' }${ !partItem.isClear && isSellableNotOwned ? ' pet-sellable-locked' : '' }` } - style={ isHead ? { backgroundSize: '200%', backgroundPosition: 'center -32px' } : undefined } + style={ isHead ? { backgroundSize: 'auto 80%', backgroundPosition: 'center', imageRendering: 'pixelated' } : undefined } { ...rest } > { !partItem.isClear && assetUrl && !isHead && diff --git a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx index 179f894..3def2bc 100644 --- a/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx +++ b/src/components/avatar-editor/figure-set/AvatarEditorFigureSetView.tsx @@ -30,12 +30,12 @@ export const AvatarEditorFigureSetView: FC<{ }; return ( - columnCount={ columnCount } estimateSize={ estimateSize } itemRender={ (item: IAvatarEditorCategoryPartItem) => + columnCount={ columnCount } itemMinWidth={ 42 } rowGap={ 8 } estimateSize={ estimateSize } itemRender={ (item: IAvatarEditorCategoryPartItem) => { if(!item) return null; return ( - selectEditorPart(category.setType, item.partSet?.id ?? -1) } /> + selectEditorPart(category.setType, item.partSet?.id ?? -1) } /> ); } } items={ category.partItems } overscan={ columnCount } /> ); diff --git a/src/components/friends/views/friends-bar/FriendBarItemView.tsx b/src/components/friends/views/friends-bar/FriendBarItemView.tsx index 239016b..61a6d42 100644 --- a/src/components/friends/views/friends-bar/FriendBarItemView.tsx +++ b/src/components/friends/views/friends-bar/FriendBarItemView.tsx @@ -33,7 +33,7 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props => { onClick={() => setVisible(prev => !prev)} >
-
{LocalizeText('friend.bar.find.title')}
+
{LocalizeText('friend.bar.find.title')}
@@ -45,10 +45,10 @@ export const FriendBarItemView: FC<{ friend: MessengerFriend }> = props => { transition={{ type: "spring", stiffness: 400, damping: 25 }} className="absolute bottom-[calc(100%+12px)] left-1/2 -translate-x-1/2 tbme-panel whitespace-nowrap z-[80] flex flex-col items-center gap-2 pointer-events-auto min-w-[170px]" > -
{LocalizeText('friend.bar.find.title')}
+
{LocalizeText('friend.bar.find.title')}
{LocalizeText('friend.bar.find.text')}