This commit is contained in:
lunaticbum 2025-08-28 18:13:46 +09:00
parent 9dd1276aa1
commit af80e71d17
4 changed files with 182 additions and 267 deletions

View File

@ -1,107 +1,71 @@
#include "Renderer.h" #include "Renderer.h"
#include "AnimationStrategy.cpp" // 애니메이션 전략 클래스들의 구현을 포함 #include "AnimationStrategy.cpp"
#include "TransitionStrategy.cpp" // 전환 효과 전략 클래스들의 구현을 포함 #include "TransitionStrategy.cpp"
#include "NoneAnimation.h"
#include "PageTurnAnimation.h" #include "PageTurnAnimation.h"
#include "ZoomAnimation.h"
#include "PanOneWayAnimation.h"
#include "PanAnimation.h" #include "PanAnimation.h"
#include "PanOneWayAnimation.h"
#include <android/log.h> #include <android/log.h>
#include <algorithm> // for std::clamp, std::max #include <algorithm>
#include <cmath> #include <cmath>
#include <thread> #include <thread>
#include <chrono> // for std::chrono::high_resolution_clock #include <chrono>
// JNI를 통해 Kotlin의 콜백 함수를 호출하기 위한 extern 선언
extern void callNextMediaCallback(); extern void callNextMediaCallback();
#define LOG_TAG "Renderer" #define LOG_TAG "Renderer"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
/** // ====================================================================
* @brief Renderer : . // 생성자, 소멸자 및 설정(Setter) 함수들
*/ // ====================================================================
Renderer::Renderer() { Renderer::Renderer() {
// 랜덤 엔진의 시드(seed)를 현재 시간으로 설정하여 매번 다른 랜덤 결과를 얻도록 합니다.
randomEngine_.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count()); randomEngine_.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count());
// 앱이 시작될 때 기본 애니메이션 및 전환 모드를 설정합니다.
setAnimationMode(static_cast<int>(AnimationMode::PAN)); setAnimationMode(static_cast<int>(AnimationMode::PAN));
setTransitionMode(static_cast<int>(TransitionMode::FADE)); setTransitionMode(static_cast<int>(TransitionMode::FADE));
} }
/**
* @brief Renderer : release .
*/
Renderer::~Renderer() { Renderer::~Renderer() {
release(); release();
} }
/**
* @brief .
*/
void Renderer::release() { void Renderer::release() {
std::lock_guard<std::mutex> lock(renderMutex_); std::lock_guard<std::mutex> lock(renderMutex_);
currentMedia_.release(); currentMedia_.release();
nextMedia_.release(); nextMedia_.release();
} }
/**
* @brief Preloader에게 .
* @param fd
*/
void Renderer::setNextMedia(int fd) { void Renderer::setNextMedia(int fd) {
preloader_.startNextPreload(fd); preloader_.startNextPreload(fd);
} }
/**
* @brief . (Kotlin에서 )
* @param speed
*/
void Renderer::setAnimationSpeed(float speed) { void Renderer::setAnimationSpeed(float speed) {
animationSpeed_ = speed > 0 ? speed : 1.0f; animationSpeed_ = speed > 0 ? speed : 1.0f;
} }
/**
* @brief / . (Kotlin에서 )
* @param durationMs
*/
void Renderer::setFadeDuration(int durationMs) { void Renderer::setFadeDuration(int durationMs) {
fadeDurationMs_ = durationMs > 0 ? durationMs : 3000; fadeDurationMs_ = durationMs > 0 ? durationMs : 3000;
} }
/**
* @brief PAGE_TURN . (Kotlin에서 )
* @param delayMs
*/
void Renderer::setPageTurnDelay(int delayMs) { void Renderer::setPageTurnDelay(int delayMs) {
pageTurnDelayMs_ = delayMs > 0 ? delayMs : 5000; pageTurnDelayMs_ = delayMs > 0 ? delayMs : 5000;
} }
/**
* @brief , .
* @param mode (int)
*/
void Renderer::setAnimationMode(int mode) { void Renderer::setAnimationMode(int mode) {
configuredAnimationMode_ = (mode >= 0 && mode <= static_cast<int>(AnimationMode::PAGE_TURN)) configuredAnimationMode_ = (mode >= 0 && mode <= static_cast<int>(AnimationMode::PAGE_TURN))
? static_cast<AnimationMode>(mode) ? static_cast<AnimationMode>(mode)
: AnimationMode::PAN; : AnimationMode::PAN;
determineActiveAnimationMode(); determineActiveAnimationMode();
} }
/**
* @brief .
* @param mode (int)
*/
void Renderer::setTransitionMode(int mode) { void Renderer::setTransitionMode(int mode) {
configuredTransitionMode_ = (mode >= 0 && mode <= static_cast<int>(TransitionMode::MOSAIC)) configuredTransitionMode_ = (mode >= 0 && mode <= static_cast<int>(TransitionMode::MOSAIC))
? static_cast<TransitionMode>(mode) ? static_cast<TransitionMode>(mode)
: TransitionMode::FADE; : TransitionMode::FADE;
} }
/**
* @brief RANDOM일 , .
*/
void Renderer::determineActiveAnimationMode() { void Renderer::determineActiveAnimationMode() {
AnimationMode modeToSetActive; AnimationMode modeToSetActive;
if (configuredAnimationMode_ == AnimationMode::RANDOM) { if (configuredAnimationMode_ == AnimationMode::RANDOM) {
@ -111,13 +75,11 @@ void Renderer::determineActiveAnimationMode() {
}; };
std::uniform_int_distribution<size_t> dist(0, availableModes.size() - 1); std::uniform_int_distribution<size_t> dist(0, availableModes.size() - 1);
modeToSetActive = availableModes[dist(randomEngine_)]; modeToSetActive = availableModes[dist(randomEngine_)];
LOGI("Random mode active: Chose animation %d", static_cast<int>(modeToSetActive));
} else { } else {
modeToSetActive = configuredAnimationMode_; modeToSetActive = configuredAnimationMode_;
} }
activeAnimationMode_ = modeToSetActive; activeAnimationMode_ = modeToSetActive;
// 결정된 모드에 맞는 '전문 요리사(전략 객체)'를 고용
switch (activeAnimationMode_) { switch (activeAnimationMode_) {
case AnimationMode::PAN: case AnimationMode::PAN:
animationStrategy_ = std::make_unique<PanAnimation>(animationSpeed_); animationStrategy_ = std::make_unique<PanAnimation>(animationSpeed_);
@ -126,148 +88,28 @@ void Renderer::determineActiveAnimationMode() {
animationStrategy_ = std::make_unique<PanOneWayAnimation>(animationSpeed_); animationStrategy_ = std::make_unique<PanOneWayAnimation>(animationSpeed_);
break; break;
case AnimationMode::ZOOM: case AnimationMode::ZOOM:
animationStrategy_ = std::make_unique<ZoomAnimation>(animationSpeed_); animationStrategy_ = std::make_unique<NoneAnimation>(animationSpeed_);
break; break;
case AnimationMode::PAGE_TURN: case AnimationMode::PAGE_TURN:
animationStrategy_ = std::make_unique<PageTurnAnimation>(animationSpeed_, pageTurnDelayMs_); animationStrategy_ = std::make_unique<PageTurnAnimation>(animationSpeed_, pageTurnDelayMs_);
break; break;
case AnimationMode::NONE: case AnimationMode::NONE:
default: default:
animationStrategy_ = std::make_unique<ZoomAnimation>(animationSpeed_); animationStrategy_ = std::make_unique<NoneAnimation>(animationSpeed_);
break;
case AnimationMode::RANDOM:
break; break;
} }
} }
/** // ====================================================================
* @brief // 상태 머신(State Machine) 핸들러 함수들
*/ // ====================================================================
void Renderer::calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight,
float& outScale, float& outOffsetX, float& outOffsetY) const {
if (!media.isValid() || media.getWidth() == 0 || media.getHeight() == 0) {
outScale = 1.0f; outOffsetX = 0.0f; outOffsetY = 0.0f;
return;
}
float mediaAspect = static_cast<float>(media.getWidth()) / media.getHeight();
float surfaceAspect = static_cast<float>(surfaceWidth) / surfaceHeight;
if (mediaAspect > surfaceAspect) {
outScale = static_cast<float>(surfaceHeight) / media.getHeight();
outOffsetX = (static_cast<float>(surfaceWidth) - (media.getWidth() * outScale)) / 2.0f;
outOffsetY = 0.0f;
} else {
outScale = static_cast<float>(surfaceWidth) / media.getWidth();
outOffsetX = 0.0f;
outOffsetY = (static_cast<float>(surfaceHeight) - (media.getHeight() * outScale)) / 2.0f;
}
}
void Renderer::handleAnimationState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight) {
/**
* @brief . ()
*/
std::string Renderer::getDebugInfo() const {
std::stringstream ss;
ss << "========== Native State ==========\n";
ss << " Configured Anim: " << static_cast<int>(configuredAnimationMode_) << "\n";
ss << " Active Anim : " << static_cast<int>(activeAnimationMode_) << "\n";
ss << " Configured Trans: " << static_cast<int>(configuredTransitionMode_) << "\n";
ss << " In Transition: " << (isInTransition_ ? "YES" : "NO") << "\n";
ss << " Anim Complete: " << (animationCycleComplete_ ? "YES" : "NO") << "\n";
if (currentMedia_.isValid()) {
ss << " Current Media: VALID ["
<< (currentMedia_.getType() == MediaAsset::Type::IMAGE ? "Image" : "Video")
<< " " << currentMedia_.getWidth() << "x" << currentMedia_.getHeight() << "]\n";
} else {
ss << " Current Media: INVALID\n";
}
if (nextMedia_.isValid()) {
ss << " Next Media : VALID ["
<< (nextMedia_.getType() == MediaAsset::Type::IMAGE ? "Image" : "Video")
<< " " << nextMedia_.getWidth() << "x" << nextMedia_.getHeight() << "]";
} else {
ss << " Next Media : INVALID";
}
return ss.str();
}
/**
* @brief . .
* @param window ()
*/
void Renderer::renderFrame(ANativeWindow* window) {
// ====================================================================
// 1단계: 공연 준비 (기본 체크 및 무대 확보)
// ====================================================================
if (!window) return;
std::lock_guard<std::mutex> lock(renderMutex_);
auto now = std::chrono::steady_clock::now();
int surfaceWidth = ANativeWindow_getWidth(window);
int surfaceHeight = ANativeWindow_getHeight(window);
// ====================================================================
// 2단계: 상태 업데이트 (그리기 전에 모든 상태를 최종 확정)
// ====================================================================
// -- 2A: 전환(Transition) 종료 처리 --
if (isInTransition_) {
long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - transitionStartTime_).count();
bool isComplete = false;
if (transitionStrategy_) isComplete = transitionStrategy_->isComplete(elapsed);
if (isComplete) {
if (nextMedia_.isValid()) {
currentMedia_ = std::move(nextMedia_);
}
isInTransition_ = false;
animationCycleComplete_ = false;
determineActiveAnimationMode();
if(animationStrategy_) animationStrategy_->reset();
isFirstFrameForMedia_ = true; // 새 미디어의 첫 프레임임을 표시
}
}
// -- 2B: 미디어 준비 --
// 현재 미디어가 없을 경우 Preloader에서 가져오기
if (!currentMedia_.isValid()) {
if (preloader_.isPreloadedDataReady()) {
currentMedia_ = preloader_.swapAndRelease();
if (currentMedia_.isValid()) {
LOGI("감독: 첫 배우 등장 준비 완료.");
isInTransition_ = false;
determineActiveAnimationMode();
if(animationStrategy_) animationStrategy_->reset();
animationCycleComplete_ = false;
isFirstFrameForMedia_ = true;
callNextMediaCallback();
}
}
}
// 그래도 무대에 세울 배우가 없다면 (아직 로딩 중) 검은 화면만 보여주고 퇴장
if (!currentMedia_.isValid()) {
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, nullptr) == 0) {
memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t));
ANativeWindow_unlockAndPost(window);
}
return;
}
// 다음 미디어가 없다면 Preloader에서 가져오기
if (!nextMedia_.isValid() && preloader_.isPreloadedDataReady()) {
nextMedia_ = preloader_.swapAndRelease();
if(nextMedia_.isValid()) {
LOGI("감독: 다음 배우 대기실에서 준비 완료.");
callNextMediaCallback();
}
}
// -- 2C: 애니메이션 진행 및 전환 시작 처리 --
AnimationState animState; AnimationState animState;
if (!isInTransition_ && !animationCycleComplete_) { if (animationStrategy_) {
float overflowX = 0.0f, overflowY = 0.0f; float overflowX = 0.0f, overflowY = 0.0f;
// PAN 계열 애니메이션은 overflow 값(이미지가 화면보다 큰 정도)이 필요
if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) { if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) {
float mediaW = static_cast<float>(currentMedia_.getWidth()); float mediaW = static_cast<float>(currentMedia_.getWidth());
float mediaH = static_cast<float>(currentMedia_.getHeight()); float mediaH = static_cast<float>(currentMedia_.getHeight());
@ -279,92 +121,125 @@ void Renderer::renderFrame(ANativeWindow* window) {
overflowY = std::max(0.0f, mediaH * scale - surfaceHeight); overflowY = std::max(0.0f, mediaH * scale - surfaceHeight);
} }
} }
animState = animationStrategy_->update(overflowX, overflowY);
// 애니메이션 전문가에게 상태 계산을 맡김
if (animationStrategy_) {
animState = animationStrategy_->update(overflowX, overflowY);
animationCycleComplete_ = animState.cycleComplete;
}
} else if (animationStrategy_) {
// 전환 중이거나 애니메이션이 끝났다면, 현재 상태를 그대로 유지
animState = animationStrategy_->update(0,0);
} }
// 애니메이션이 끝났고, 다음 미디어가 있고, 첫 프레임이 아니라면 -> 전환 시작! memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t));
if (animationCycleComplete_ && !isInTransition_ && nextMedia_.isValid() && !isFirstFrameForMedia_) { float finalOffsetX, finalOffsetY, finalScale;
isInTransition_ = true; if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) {
transitionStartTime_ = now; float scale;
if ((static_cast<float>(currentMedia_.getWidth()) / currentMedia_.getHeight()) > (static_cast<float>(surfaceWidth) / surfaceHeight)) {
scale = static_cast<float>(surfaceHeight) / currentMedia_.getHeight();
} else {
scale = static_cast<float>(surfaceWidth) / currentMedia_.getWidth();
}
finalScale = scale * animState.scale;
finalOffsetX = animState.offsetX;
finalOffsetY = animState.offsetY;
} else {
float baseScale, baseOffsetX, baseOffsetY;
calculateFitScaleAndOffset(currentMedia_, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY);
finalScale = baseScale * animState.scale;
finalOffsetX = baseOffsetX + animState.offsetX;
finalOffsetY = baseOffsetY + animState.offsetY;
}
drawMedia(buffer, currentMedia_, 1.0f, finalOffsetX, finalOffsetY, finalScale);
if (animState.cycleComplete && nextMedia_.isValid()) {
currentState_ = RenderState::TRANSITIONING;
transitionStartTime_ = std::chrono::steady_clock::now();
// 설정된 전환 모드를 확인
TransitionMode transModeToUse = configuredTransitionMode_; TransitionMode transModeToUse = configuredTransitionMode_;
if (transModeToUse == TransitionMode::RANDOM) { if (transModeToUse == TransitionMode::RANDOM) {
std::uniform_int_distribution<int> dist(0, 1); // 0:FADE, 1:SLIDE std::uniform_int_distribution<int> dist(0, 2);
transModeToUse = static_cast<TransitionMode>(dist(randomEngine_)); transModeToUse = static_cast<TransitionMode>(dist(randomEngine_));
} }
// 모드에 맞는 전환 전문가 객체 생성
if (transModeToUse == TransitionMode::SLIDE) { if (transModeToUse == TransitionMode::SLIDE) {
transitionStrategy_ = std::make_unique<SlideTransition>(fadeDurationMs_, surfaceWidth); transitionStrategy_ = std::make_unique<SlideTransition>(fadeDurationMs_, surfaceWidth);
} else if (transModeToUse == TransitionMode::FADE) { // 기본값 및 MOSAIC 대체는 FADE } else if (transModeToUse == TransitionMode::MOSAIC) {
transitionStrategy_ = std::make_unique<MosaicTransition>(fadeDurationMs_, 20, 32, randomEngine_);
} else {
transitionStrategy_ = std::make_unique<FadeTransition>(fadeDurationMs_); transitionStrategy_ = std::make_unique<FadeTransition>(fadeDurationMs_);
}else if (transModeToUse == TransitionMode::MOSAIC) { // 기본값 및 MOSAIC 대체는 FADE
transitionStrategy_ = std::make_unique<MosaicTransition>(fadeDurationMs_,30,30,randomEngine_);
} }
if(transitionStrategy_) transitionStrategy_->reset(); if(transitionStrategy_) transitionStrategy_->reset();
} }
}
void Renderer::handleTransitionState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight) {
if (!transitionStrategy_ || !nextMedia_.isValid()) {
currentState_ = RenderState::ANIMATING;
if(animationStrategy_) animationStrategy_->reset();
return;
}
auto now = std::chrono::steady_clock::now();
long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - transitionStartTime_).count();
// 1. 상태 보고: isComplete()를 호출해서 끝났는지 "물어보기만" 함
bool isComplete = transitionStrategy_->isComplete(elapsed);
transitionStrategy_->execute(this, buffer, currentMedia_, nextMedia_, elapsed);
if (isComplete) {
currentMedia_ = std::move(nextMedia_);
currentState_ = RenderState::ANIMATING;
determineActiveAnimationMode();
if(animationStrategy_) animationStrategy_->reset();
}
}
// ====================================================================
// 메인 렌더링 루프 (교통정리 담당)
// ====================================================================
void Renderer::renderFrame(ANativeWindow* window) {
if (!window) return;
std::lock_guard<std::mutex> lock(renderMutex_);
if (!currentMedia_.isValid() && preloader_.isPreloadedDataReady()) {
currentMedia_ = preloader_.swapAndRelease();
if (currentMedia_.isValid()) {
currentState_ = RenderState::ANIMATING;
determineActiveAnimationMode();
if(animationStrategy_) animationStrategy_->reset();
callNextMediaCallback();
}
}
if (!currentMedia_.isValid()) {
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, nullptr) == 0) {
memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t));
ANativeWindow_unlockAndPost(window);
}
return;
}
if (!nextMedia_.isValid() && preloader_.isPreloadedDataReady()) {
nextMedia_ = preloader_.swapAndRelease();
if(nextMedia_.isValid()) { callNextMediaCallback(); }
}
// ====================================================================
// 3단계: 그리기 (위에서 확정된 최종 상태를 기반으로 그림)
// ====================================================================
ANativeWindow_Buffer buffer; ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(window, &buffer, nullptr) != 0) return; if (ANativeWindow_lock(window, &buffer, nullptr) != 0) return;
int surfaceWidth = ANativeWindow_getWidth(window);
int surfaceHeight = ANativeWindow_getHeight(window);
// -- 3A: 장면 전환(Transition)이 진행 중일 때 그리기 -- switch (currentState_) {
if (isInTransition_ && transitionStrategy_ && nextMedia_.isValid()) { case RenderState::ANIMATING:
long long elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - transitionStartTime_).count(); handleAnimationState(buffer, surfaceWidth, surfaceHeight);
// 전문가에게 무대(buffer)를 넘겨주고 모든 그리기를 위임 break;
transitionStrategy_->execute(this, buffer, currentMedia_, nextMedia_, elapsed); case RenderState::TRANSITIONING:
} handleTransitionState(buffer, surfaceWidth, surfaceHeight);
// -- 3B: 일반 연기(Animation)가 진행 중일 때 그리기 -- break;
else {
memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t));
float finalOffsetX, finalOffsetY, finalScale;
// PAN 계열과 그 외 모드의 레이아웃 계산을 명확히 분리
if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) {
// --- ⬇️ PAN 모드일 때 이 로그가 찍혀야 합니다 ⬇️ ---
float scale;
if ((static_cast<float>(currentMedia_.getWidth()) / currentMedia_.getHeight()) > (static_cast<float>(surfaceWidth) / surfaceHeight)) {
scale = static_cast<float>(surfaceHeight) / currentMedia_.getHeight();
} else {
scale = static_cast<float>(surfaceWidth) / currentMedia_.getWidth();
}
finalScale = scale * animState.scale;
finalOffsetX = animState.offsetX;
finalOffsetY = animState.offsetY;
} else {
// --- ⬇️ 그 외 모드일 때 이 로그가 찍혀야 합니다 ⬇️ ---
float baseScale, baseOffsetX, baseOffsetY;
calculateFitScaleAndOffset(currentMedia_, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY);
finalScale = baseScale * animState.scale;
finalOffsetX = baseOffsetX + animState.offsetX;
finalOffsetY = baseOffsetY + animState.offsetY;
}
drawMedia(buffer, currentMedia_, 1.0f, finalOffsetX, finalOffsetY, finalScale);
} }
ANativeWindow_unlockAndPost(window); ANativeWindow_unlockAndPost(window);
isFirstFrameForMedia_ = false; // 프레임 그리기가 끝났으므로 첫 프레임 플래그를 내림
} }
/** // ====================================================================
* @brief . . // 하위 그리기 함수 및 헬퍼 함수들
*/ // ====================================================================
void Renderer::drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale) { void Renderer::drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale) {
if (!media.isValid() || alpha <= 0.0f) return; if (!media.isValid() || alpha <= 0.0f) return;
if (media.getType() == MediaAsset::Type::IMAGE) { if (media.getType() == MediaAsset::Type::IMAGE) {
renderImageFrame(media, buffer, finalScale, finalOffsetX, finalOffsetY, alpha); renderImageFrame(media, buffer, finalScale, finalOffsetX, finalOffsetY, alpha);
} else { } else {
@ -372,9 +247,6 @@ void Renderer::drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float
} }
} }
/**
* @brief .
*/
void Renderer::renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) { void Renderer::renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) {
uint32_t* dstPixels = (uint32_t*)buffer.bits; uint32_t* dstPixels = (uint32_t*)buffer.bits;
int dstStride = buffer.stride; int dstStride = buffer.stride;
@ -386,35 +258,25 @@ void Renderer::renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& b
uint8_t alphaByte = static_cast<uint8_t>(alpha * 255.0f); uint8_t alphaByte = static_cast<uint8_t>(alpha * 255.0f);
for (int y = 0; y < buffer.height; ++y) { for (int y = 0; y < buffer.height; ++y) {
// --- ⬇️ 이 부분이 최종 수정된 올바른 좌표 계산입니다 ⬇️ ---
int srcY = static_cast<int>((y + offsetY) / scale); int srcY = static_cast<int>((y + offsetY) / scale);
if (srcY < 0 || srcY >= imgH) continue; if (srcY < 0 || srcY >= imgH) continue;
uint32_t* dstRow = dstPixels + y * dstStride; uint32_t* dstRow = dstPixels + y * dstStride;
for (int x = 0; x < buffer.width; ++x) { for (int x = 0; x < buffer.width; ++x) {
// --- ⬇️ 이 부분이 최종 수정된 올바른 좌표 계산입니다 ⬇️ ---
int srcX = static_cast<int>((x + offsetX) / scale); int srcX = static_cast<int>((x + offsetX) / scale);
if (srcX < 0 || srcX >= imgW) continue; if (srcX < 0 || srcX >= imgW) continue;
const uint8_t* srcPixel = &pixelData[(srcY * imgW + srcX) * 4]; const uint8_t* srcPixel = &pixelData[(srcY * imgW + srcX) * 4];
uint32_t dstPixelValue = dstRow[x]; uint32_t dstPixelValue = dstRow[x];
uint8_t dstB = (dstPixelValue >> 0) & 0xFF; uint8_t dstB = (dstPixelValue >> 0) & 0xFF;
uint8_t dstG = (dstPixelValue >> 8) & 0xFF; uint8_t dstG = (dstPixelValue >> 8) & 0xFF;
uint8_t dstR = (dstPixelValue >> 16) & 0xFF; uint8_t dstR = (dstPixelValue >> 16) & 0xFF;
uint8_t finalR = (srcPixel[0] * alphaByte + dstR * (255 - alphaByte)) / 255; uint8_t finalR = (srcPixel[0] * alphaByte + dstR * (255 - alphaByte)) / 255;
uint8_t finalG = (srcPixel[1] * alphaByte + dstG * (255 - alphaByte)) / 255; uint8_t finalG = (srcPixel[1] * alphaByte + dstG * (255 - alphaByte)) / 255;
uint8_t finalB = (srcPixel[2] * alphaByte + dstB * (255 - alphaByte)) / 255; uint8_t finalB = (srcPixel[2] * alphaByte + dstB * (255 - alphaByte)) / 255;
dstRow[x] = (0xFF << 24) | (finalR << 16) | (finalG << 8) | finalB; dstRow[x] = (0xFF << 24) | (finalR << 16) | (finalG << 8) | finalB;
} }
} }
} }
/**
* @brief , renderImageFrame을 .
*/
void Renderer::renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) { void Renderer::renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha) {
AVFormatContext* fmt_ctx = media.getFormatContext(); AVFormatContext* fmt_ctx = media.getFormatContext();
AVCodecContext* codec_ctx = media.getCodecContext(); AVCodecContext* codec_ctx = media.getCodecContext();
@ -443,4 +305,51 @@ void Renderer::renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer,
} }
renderImageFrame(media, buffer, scale, offsetX, offsetY, alpha); renderImageFrame(media, buffer, scale, offsetX, offsetY, alpha);
}
void Renderer::calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight, float& outScale, float& outOffsetX, float& outOffsetY) const {
if (!media.isValid() || media.getWidth() == 0 || media.getHeight() == 0) {
outScale = 1.0f; outOffsetX = 0.0f; outOffsetY = 0.0f;
return;
}
float mediaAspect = static_cast<float>(media.getWidth()) / media.getHeight();
float surfaceAspect = static_cast<float>(surfaceWidth) / surfaceHeight;
if (mediaAspect > surfaceAspect) {
outScale = static_cast<float>(surfaceHeight) / media.getHeight();
outOffsetX = (static_cast<float>(surfaceWidth) - (media.getWidth() * outScale)) / 2.0f;
outOffsetY = 0.0f;
} else {
outScale = static_cast<float>(surfaceWidth) / media.getWidth();
outOffsetX = 0.0f;
outOffsetY = (static_cast<float>(surfaceHeight) - (media.getHeight() * outScale)) / 2.0f;
}
}
/**
* @brief . ()
*/
std::string Renderer::getDebugInfo() const {
std::stringstream ss;
ss << "========== Native State ==========\n";
ss << " Configured Anim: " << static_cast<int>(configuredAnimationMode_) << "\n";
ss << " Active Anim : " << static_cast<int>(activeAnimationMode_) << "\n";
ss << " Configured Trans: " << static_cast<int>(configuredTransitionMode_) << "\n";
ss << " In CurrentState: " << static_cast<int>(currentState_) << "\n";
if (currentMedia_.isValid()) {
ss << " Current Media: VALID ["
<< (currentMedia_.getType() == MediaAsset::Type::IMAGE ? "Image" : "Video")
<< " " << currentMedia_.getWidth() << "x" << currentMedia_.getHeight() << "]\n";
} else {
ss << " Current Media: INVALID\n";
}
if (nextMedia_.isValid()) {
ss << " Next Media : VALID ["
<< (nextMedia_.getType() == MediaAsset::Type::IMAGE ? "Image" : "Video")
<< " " << nextMedia_.getWidth() << "x" << nextMedia_.getHeight() << "]";
} else {
ss << " Next Media : INVALID";
}
return ss.str();
} }

View File

@ -49,19 +49,27 @@ public:
void setPageTurnDelay(int delayMs); void setPageTurnDelay(int delayMs);
void setTransitionMode(int mode); void setTransitionMode(int mode);
void calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight,
float& outScale, float& outOffsetX, float& outOffsetY) const;
;
// --- 전략(Strategy) 객체들이 호출하는 헬퍼 함수들 --- // --- 전략(Strategy) 객체들이 호출하는 헬퍼 함수들 ---
// (private에서 public으로 이동) void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale);
void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float offsetX, float offsetY, float scaleMultiplier);
void renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha); void renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
void renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha); void renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& buffer, float scale, float offsetX, float offsetY, float alpha);
void calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight,
float& outScale, float& outOffsetX, float& outOffsetY) const;
private: private:
std::mutex renderMutex_; // --- 상태 머신(State Machine) ---
enum class RenderState {
ANIMATING,
TRANSITIONING
};
RenderState currentState_ = RenderState::ANIMATING;
// --- 상태별 처리를 위한 private 핸들러 함수 ---
void handleAnimationState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight);
void handleTransitionState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight);
// --- 멤버 객체들 --- // --- 멤버 객체들 ---
std::mutex renderMutex_;
Preloader preloader_; Preloader preloader_;
MediaAsset currentMedia_; MediaAsset currentMedia_;
MediaAsset nextMedia_; MediaAsset nextMedia_;
@ -77,16 +85,11 @@ private:
float animationSpeed_ = 1.0f; float animationSpeed_ = 1.0f;
// --- 상태(State) 관리 변수 --- // --- 상태(State) 관리 변수 ---
bool isInTransition_ = false;
std::chrono::steady_clock::time_point transitionStartTime_; std::chrono::steady_clock::time_point transitionStartTime_;
bool animationCycleComplete_ = false;
bool isFirstFrameForMedia_ = true; // 새 미디어의 첫 프레임인지 확인하는 플래그
// --- 랜덤 기능 --- // --- 랜덤 기능 ---
std::mt19937 randomEngine_; std::mt19937 randomEngine_;
// --- private 헬퍼 함수 --- // --- private 헬퍼 함수 ---
void determineActiveAnimationMode(); void determineActiveAnimationMode();
}; };

View File

@ -101,7 +101,10 @@ Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartNextPreload(JNIEnv* en
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetAnimationSpeed(JNIEnv* env, jobject, jlong nativeHandle, jfloat speed) { Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetAnimationSpeed(JNIEnv* env, jobject, jlong nativeHandle, jfloat speed) {
// ... Renderer* renderer = toNative<Renderer>(nativeHandle);
if (renderer) {
renderer->setAnimationSpeed(speed);
}
} }
// --- ⬇️ 새로 추가할 JNI 함수들 ⬇️ --- // --- ⬇️ 새로 추가할 JNI 함수들 ⬇️ ---

View File

@ -183,9 +183,9 @@ class MyWallpaperService : WallpaperService() {
nativeRenderer?.initialize() // nativeInit() -> initialize() nativeRenderer?.initialize() // nativeInit() -> initialize()
nativeRenderer?.setFadeDuration(1500) nativeRenderer?.setFadeDuration(1500)
nativeRenderer?.setTurnPageDuration(8000) nativeRenderer?.setTurnPageDuration(8000)
nativeRenderer?.setAnimationMode(NativeRenderer.ANIMATION_MODE_RANDOM) nativeRenderer?.setAnimationMode(NativeRenderer.ANIMATION_MODE_PAN)
nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_RANDOM) nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_FADE)
nativeRenderer?.setAnimationSpeed(1.0f) // nativeSetAnimationSpeed -> setAnimationSpeed nativeRenderer?.setAnimationSpeed(10.0f) // nativeSetAnimationSpeed -> setAnimationSpeed
NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback) NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback)