Skip to content

feat(pngtuber): Implement PNGtuber feature#890

Open
ozekimasaki wants to merge 17 commits intomoeru-ai:mainfrom
ozekimasaki:ozekimasaki/feat/pngtuber-support
Open

feat(pngtuber): Implement PNGtuber feature#890
ozekimasaki wants to merge 17 commits intomoeru-ai:mainfrom
ozekimasaki:ozekimasaki/feat/pngtuber-support

Conversation

@ozekimasaki
Copy link

@ozekimasaki ozekimasaki commented Jan 6, 2026

Overview

Implements PNGtuber feature that provides 2D PNG image-based avatar display and animation functionality.

Main Changes

New Additions

  • PNGtuber scene component (packages/stage-ui/src/components/scenes/PNGtuber.vue)\n - Lip sync support\n - Auto blink functionality\n - Emotion expression support\n - Pack animation (mouth movement)\n- PNGtuber settings component (packages/stage-ui/src/components/scenarios/settings/model-settings/PNGtuber.vue)\n - Scale and position adjustment\n- PNGtuber ZIP file loader (packages/stage-ui/src/utils/pngtuber-zip-loader.ts)\n - Model loading from ZIP files\n - Manifest parsing\n- PNGtuber store (packages/stage-ui/src/stores/pngtuber.ts)\n - State management (position, scale, emotion, blink settings, etc.)\n- Emotion constants addition (packages/stage-ui/src/constants/emotions.ts)\n- Sample assets addition\n

Integration

  • PNGtuber integration into model selector\n- PNGtuber settings addition to model settings page\n- PNGtuber scene integration into Stage component\n- Preview generation logic unification and improvements\n

Improvements

  • Performance improvement through synchronous image cache\n- Blink state management optimization\n- Resource cleanup improvements\n- Enhanced error handling\n

Change Statistics

  • 13 files changed\n- 1,074 lines added\n- 75 lines deleted\n

Testing

  • Basic display functionality\n- [ ] Loading from ZIP files\n- [ ] Lip sync behavior\n- [ ] Blink animation\n- [ ] Emotion switching\n- [ ] Settings save/load

…onstants

- Move animation constants (BLINK_*, MOUTH_PAC_*, CANVAS_RESOLUTION_*) before their usage
- Replace magic numbers with named constants for better maintainability
- Add ResizeObserver for dynamic canvas resizing
- Improve canvas resize handling with size validation
…error handling

- Extract common canvas creation logic into createPreviewCanvas utility
- Extract common cropping and padding logic into cropAndPadPreview utility
- Standardize error messages across preview generation functions
- Replace magic numbers with named constants (PREVIEW_*, LIVE2D_*, VRM_*)
- Improve type safety with clearer return types and null checks
- Add logic to regenerate missing PNGtuber previews from IndexedDB
- Remove console.log statements from model selector dialog
- Remove console.log statements from PNGtuber ZIP loader
- Clean up development-only debugging code
- Add pngtuber-scene-class prop to ModelSettings component
- Ensure PNGtuberScene renders correctly in model settings view
@ozekimasaki ozekimasaki changed the title feat(pngtuber): PNGtuber機能の実装 feat(pngtuber): Implement PNGtuber feature Jan 6, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ozekimasaki, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

このプルリクエストは、アプリケーションにPNGtuber機能を追加することを目的としています。これにより、ユーザーは2D PNG画像ベースのアバターをロードし、リップシンク、自動まばたき、感情表現などの動的なアニメーションをステージ上で表示できるようになります。この機能は、既存のモデル選択および設定システムにシームレスに統合され、パフォーマンスと安定性も向上しています。

Highlights

  • PNGtuber機能の追加: 2D PNG画像ベースのアバター表示とアニメーション機能(リップシンク、自動まばたき、感情表現、口パク)を実装しました。
  • 設定と統合: PNGtuberモデルの設定(スケール、位置調整)コンポーネントを追加し、モデルセレクター、モデル設定ページ、およびメインのStageコンポーネントに統合しました。
  • ZIPファイルからの読み込み: ZIPファイルからPNGtuberモデルを読み込み、マニフェストを解析するユーティリティを実装しました。
  • 状態管理: PNGtuberの状態(位置、スケール、感情、まばたき設定など)を管理するためのPiniaストアを導入しました。
  • プレビュー生成の改善: Live2DおよびVRMモデルのプレビュー生成ロジックを統一し、PNGtuberモデルのプレビュー生成機能を追加しました。
  • パフォーマンスと安定性の向上: 画像キャッシュの同期化、まばたき状態管理の最適化、リソースクリーンアップ、エラーハンドリングの強化を行いました。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully implements the new PNGtuber feature, including the necessary scene components, settings, store for state management, and ZIP file loader. The integration with the existing model selection and stage components is well-executed. My review includes a few suggestions to enhance maintainability and animation loop consistency, such as using more specific i18n keys, simplifying some logic in the rendering component, and refactoring the blink animation to be fully managed within requestAnimationFrame. Overall, this is a solid contribution that adds significant new functionality.


<template>
<Section
:title="t('settings.live2d.scale-and-position.title')"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The internationalization (i18n) keys used in this component appear to be copied from the Live2D settings. For better maintainability and to avoid potential conflicts, it's recommended to use keys specific to PNGtuber, such as settings.pngtuber.scale-and-position.title. This should be applied to all labels within this component.

    :title="t('settings.pngtuber.scale-and-position.title')"

Comment on lines +81 to +82
const baseSprite = (emotionData && typeof emotionData !== 'string' ? emotionData.default : null) ?? manifest.value.idle.default
const blinkSprite = (emotionData && typeof emotionData !== 'string' ? emotionData.blink : null) ?? manifest.value.idle.blink
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check typeof emotionData !== 'string' is redundant, as the PNGtuberManifest type defines emotions as a record of objects. emotionData will therefore be an object or undefined, never a string. You can simplify this logic using optional chaining for better readability.

  const baseSprite = emotionData?.default ?? manifest.value.idle.default
  const blinkSprite = emotionData?.blink ?? manifest.value.idle.blink

Comment on lines +216 to +227
// Update blink state - only schedule a new blink if not already blinking
if (!blinkState.value.isBlinking) {
blinkState.value.blinkTimer += deltaTime
if (blinkState.value.blinkTimer >= blinkState.value.nextBlinkTime) {
blinkState.value.isBlinking = true
setTimeout(() => {
blinkState.value.isBlinking = false
blinkState.value.blinkTimer = 0
blinkState.value.nextBlinkTime = BLINK_INTERVAL_MIN_MS + Math.random() * (BLINK_INTERVAL_MAX_MS - BLINK_INTERVAL_MIN_MS)
}, BLINK_DURATION_MS)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using setTimeout within a requestAnimationFrame loop can lead to animation synchronization issues. It's best practice to manage all animation timing within the render loop using the provided timestamp and deltaTime.

To improve this, you could add a blinkEndTime to your blinkState ref:

const blinkState = ref({
  // ... other properties
  blinkEndTime: 0,
})

Then, refactor the blink logic to be fully managed by the render loop, like so:

  // Update blink state
  if (blinkState.value.isBlinking) {
    if (timestamp >= blinkState.value.blinkEndTime) {
      blinkState.value.isBlinking = false
      blinkState.value.blinkTimer = 0
      blinkState.value.nextBlinkTime = BLINK_INTERVAL_MIN_MS + Math.random() * (BLINK_INTERVAL_MAX_MS - BLINK_INTERVAL_MIN_MS)
    }
  }
  else {
    blinkState.value.blinkTimer += deltaTime
    if (blinkState.value.blinkTimer >= blinkState.value.nextBlinkTime) {
      blinkState.value.isBlinking = true
      blinkState.value.blinkEndTime = timestamp + BLINK_DURATION_MS
    }
  }

@github-actions
Copy link
Contributor

⏳ Approval required for deploying to Cloudflare Workers (Preview) for stage-web.

Name Link
🔭 Waiting for approval For maintainers, approve here

Hey, @nekomeowww, @sumimakito, @luoling8192, @LemonNekoGH, kindly take some time to review and approve this deployment when you are available. Thank you! 🙏

@nekomeowww
Copy link
Member

Rebase needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants