diff --git a/app/src/main/cpp/Renderer.cpp b/app/src/main/cpp/Renderer.cpp index 1162c45c..c798bb9c 100644 --- a/app/src/main/cpp/Renderer.cpp +++ b/app/src/main/cpp/Renderer.cpp @@ -1,107 +1,71 @@ #include "Renderer.h" -#include "AnimationStrategy.cpp" // 애니메이션 전략 클래스들의 구현을 포함 -#include "TransitionStrategy.cpp" // 전환 효과 전략 클래스들의 구현을 포함 +#include "AnimationStrategy.cpp" +#include "TransitionStrategy.cpp" +#include "NoneAnimation.h" #include "PageTurnAnimation.h" -#include "ZoomAnimation.h" -#include "PanOneWayAnimation.h" #include "PanAnimation.h" +#include "PanOneWayAnimation.h" #include -#include // for std::clamp, std::max +#include #include #include -#include // for std::chrono::high_resolution_clock +#include -// JNI를 통해 Kotlin의 콜백 함수를 호출하기 위한 extern 선언 extern void callNextMediaCallback(); #define LOG_TAG "Renderer" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) -/** - * @brief Renderer 생성자: 멤버 변수를 초기화합니다. - */ +// ==================================================================== +// 생성자, 소멸자 및 설정(Setter) 함수들 +// ==================================================================== + Renderer::Renderer() { - // 랜덤 엔진의 시드(seed)를 현재 시간으로 설정하여 매번 다른 랜덤 결과를 얻도록 합니다. randomEngine_.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count()); - // 앱이 시작될 때 기본 애니메이션 및 전환 모드를 설정합니다. setAnimationMode(static_cast(AnimationMode::PAN)); setTransitionMode(static_cast(TransitionMode::FADE)); } -/** - * @brief Renderer 소멸자: release 함수를 호출하여 자원을 정리합니다. - */ Renderer::~Renderer() { release(); } -/** - * @brief 모든 미디어 에셋 관련 자원을 해제합니다. - */ void Renderer::release() { std::lock_guard lock(renderMutex_); currentMedia_.release(); nextMedia_.release(); } -/** - * @brief Preloader에게 다음 미디어를 미리 로드하라고 지시합니다. - * @param fd 로드할 파일의 파일 디스크립터 - */ void Renderer::setNextMedia(int fd) { preloader_.startNextPreload(fd); } -/** - * @brief 애니메이션 속도를 설정합니다. (Kotlin에서 호출) - * @param speed 프레임당 이동할 픽셀 수 또는 줌 속도 배율 - */ void Renderer::setAnimationSpeed(float speed) { animationSpeed_ = speed > 0 ? speed : 1.0f; } -/** - * @brief 페이드/전환 시간을 설정합니다. (Kotlin에서 호출) - * @param durationMs 밀리초 단위의 시간 - */ void Renderer::setFadeDuration(int durationMs) { fadeDurationMs_ = durationMs > 0 ? durationMs : 3000; } -/** - * @brief PAGE_TURN 모드의 대기 시간을 설정합니다. (Kotlin에서 호출) - * @param delayMs 밀리초 단위의 시간 - */ void Renderer::setPageTurnDelay(int delayMs) { pageTurnDelayMs_ = delayMs > 0 ? delayMs : 5000; } -/** - * @brief 애니메이션 모드를 설정하고, 해당 모드에 맞는 전략 객체를 생성합니다. - * @param mode 설정할 애니메이션 모드 (int) - */ void Renderer::setAnimationMode(int mode) { configuredAnimationMode_ = (mode >= 0 && mode <= static_cast(AnimationMode::PAGE_TURN)) ? static_cast(mode) : AnimationMode::PAN; - determineActiveAnimationMode(); } -/** - * @brief 전환 모드를 설정합니다. - * @param mode 설정할 전환 모드 (int) - */ void Renderer::setTransitionMode(int mode) { configuredTransitionMode_ = (mode >= 0 && mode <= static_cast(TransitionMode::MOSAIC)) ? static_cast(mode) : TransitionMode::FADE; } -/** - * @brief 설정된 모드가 RANDOM일 경우, 실제 적용할 애니메이션을 무작위로 결정합니다. - */ void Renderer::determineActiveAnimationMode() { AnimationMode modeToSetActive; if (configuredAnimationMode_ == AnimationMode::RANDOM) { @@ -111,13 +75,11 @@ void Renderer::determineActiveAnimationMode() { }; std::uniform_int_distribution dist(0, availableModes.size() - 1); modeToSetActive = availableModes[dist(randomEngine_)]; - LOGI("Random mode active: Chose animation %d", static_cast(modeToSetActive)); } else { modeToSetActive = configuredAnimationMode_; } - activeAnimationMode_ = modeToSetActive; - // 결정된 모드에 맞는 '전문 요리사(전략 객체)'를 고용 + switch (activeAnimationMode_) { case AnimationMode::PAN: animationStrategy_ = std::make_unique(animationSpeed_); @@ -126,148 +88,28 @@ void Renderer::determineActiveAnimationMode() { animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::ZOOM: - animationStrategy_ = std::make_unique(animationSpeed_); + animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::PAGE_TURN: animationStrategy_ = std::make_unique(animationSpeed_, pageTurnDelayMs_); break; case AnimationMode::NONE: default: - animationStrategy_ = std::make_unique(animationSpeed_); + animationStrategy_ = std::make_unique(animationSpeed_); + break; + case AnimationMode::RANDOM: break; } } -/** - * @brief 화면에 꽉 차도록 비율에 맞춰 스케일과 중앙 정렬 오프셋을 계산하는 헬퍼 함수 - */ -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(media.getWidth()) / media.getHeight(); - float surfaceAspect = static_cast(surfaceWidth) / surfaceHeight; - if (mediaAspect > surfaceAspect) { - outScale = static_cast(surfaceHeight) / media.getHeight(); - outOffsetX = (static_cast(surfaceWidth) - (media.getWidth() * outScale)) / 2.0f; - outOffsetY = 0.0f; - } else { - outScale = static_cast(surfaceWidth) / media.getWidth(); - outOffsetX = 0.0f; - outOffsetY = (static_cast(surfaceHeight) - (media.getHeight() * outScale)) / 2.0f; - } -} +// ==================================================================== +// 상태 머신(State Machine) 핸들러 함수들 +// ==================================================================== - -/** - * @brief 현재 렌더러의 내부 상태를 문자열로 반환합니다. (디버깅용) - */ -std::string Renderer::getDebugInfo() const { - std::stringstream ss; - ss << "========== Native State ==========\n"; - ss << " Configured Anim: " << static_cast(configuredAnimationMode_) << "\n"; - ss << " Active Anim : " << static_cast(activeAnimationMode_) << "\n"; - ss << " Configured Trans: " << static_cast(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 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(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: 애니메이션 진행 및 전환 시작 처리 -- +void Renderer::handleAnimationState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight) { AnimationState animState; - if (!isInTransition_ && !animationCycleComplete_) { + if (animationStrategy_) { float overflowX = 0.0f, overflowY = 0.0f; - // PAN 계열 애니메이션은 overflow 값(이미지가 화면보다 큰 정도)이 필요 if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) { float mediaW = static_cast(currentMedia_.getWidth()); float mediaH = static_cast(currentMedia_.getHeight()); @@ -279,92 +121,125 @@ void Renderer::renderFrame(ANativeWindow* window) { overflowY = std::max(0.0f, mediaH * scale - surfaceHeight); } } - - // 애니메이션 전문가에게 상태 계산을 맡김 - if (animationStrategy_) { - animState = animationStrategy_->update(overflowX, overflowY); - animationCycleComplete_ = animState.cycleComplete; - } - } else if (animationStrategy_) { - // 전환 중이거나 애니메이션이 끝났다면, 현재 상태를 그대로 유지 - animState = animationStrategy_->update(0,0); + animState = animationStrategy_->update(overflowX, overflowY); } - // 애니메이션이 끝났고, 다음 미디어가 있고, 첫 프레임이 아니라면 -> 전환 시작! - if (animationCycleComplete_ && !isInTransition_ && nextMedia_.isValid() && !isFirstFrameForMedia_) { - isInTransition_ = true; - transitionStartTime_ = now; + memset(buffer.bits, 0, buffer.stride * buffer.height * sizeof(uint32_t)); + float finalOffsetX, finalOffsetY, finalScale; + if (activeAnimationMode_ == AnimationMode::PAN || activeAnimationMode_ == AnimationMode::PAN_ONE_WAY) { + float scale; + if ((static_cast(currentMedia_.getWidth()) / currentMedia_.getHeight()) > (static_cast(surfaceWidth) / surfaceHeight)) { + scale = static_cast(surfaceHeight) / currentMedia_.getHeight(); + } else { + scale = static_cast(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_; if (transModeToUse == TransitionMode::RANDOM) { - std::uniform_int_distribution dist(0, 1); // 0:FADE, 1:SLIDE + std::uniform_int_distribution dist(0, 2); transModeToUse = static_cast(dist(randomEngine_)); } - // 모드에 맞는 전환 전문가 객체 생성 if (transModeToUse == TransitionMode::SLIDE) { transitionStrategy_ = std::make_unique(fadeDurationMs_, surfaceWidth); - } else if (transModeToUse == TransitionMode::FADE) { // 기본값 및 MOSAIC 대체는 FADE + } else if (transModeToUse == TransitionMode::MOSAIC) { + transitionStrategy_ = std::make_unique(fadeDurationMs_, 20, 32, randomEngine_); + } else { transitionStrategy_ = std::make_unique(fadeDurationMs_); - }else if (transModeToUse == TransitionMode::MOSAIC) { // 기본값 및 MOSAIC 대체는 FADE - transitionStrategy_ = std::make_unique(fadeDurationMs_,30,30,randomEngine_); } 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(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 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; if (ANativeWindow_lock(window, &buffer, nullptr) != 0) return; + int surfaceWidth = ANativeWindow_getWidth(window); + int surfaceHeight = ANativeWindow_getHeight(window); - // -- 3A: 장면 전환(Transition)이 진행 중일 때 그리기 -- - if (isInTransition_ && transitionStrategy_ && nextMedia_.isValid()) { - long long elapsed = std::chrono::duration_cast(now - transitionStartTime_).count(); - // 전문가에게 무대(buffer)를 넘겨주고 모든 그리기를 위임 - transitionStrategy_->execute(this, buffer, currentMedia_, nextMedia_, elapsed); - } - // -- 3B: 일반 연기(Animation)가 진행 중일 때 그리기 -- - 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(currentMedia_.getWidth()) / currentMedia_.getHeight()) > (static_cast(surfaceWidth) / surfaceHeight)) { - scale = static_cast(surfaceHeight) / currentMedia_.getHeight(); - } else { - scale = static_cast(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); + switch (currentState_) { + case RenderState::ANIMATING: + handleAnimationState(buffer, surfaceWidth, surfaceHeight); + break; + case RenderState::TRANSITIONING: + handleTransitionState(buffer, surfaceWidth, surfaceHeight); + break; } ANativeWindow_unlockAndPost(window); - isFirstFrameForMedia_ = false; // 프레임 그리기가 끝났으므로 첫 프레임 플래그를 내림 } -/** - * @brief 지시를 수행하는 스태프. 이제 계산 없이 감독에게 받은 최종 값으로 그리기만 합니다. - */ +// ==================================================================== +// 하위 그리기 함수 및 헬퍼 함수들 +// ==================================================================== + 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.getType() == MediaAsset::Type::IMAGE) { renderImageFrame(media, buffer, finalScale, finalOffsetX, finalOffsetY, alpha); } 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) { uint32_t* dstPixels = (uint32_t*)buffer.bits; int dstStride = buffer.stride; @@ -386,35 +258,25 @@ void Renderer::renderImageFrame(const MediaAsset& media, ANativeWindow_Buffer& b uint8_t alphaByte = static_cast(alpha * 255.0f); for (int y = 0; y < buffer.height; ++y) { - // --- ⬇️ 이 부분이 최종 수정된 올바른 좌표 계산입니다 ⬇️ --- int srcY = static_cast((y + offsetY) / scale); if (srcY < 0 || srcY >= imgH) continue; - uint32_t* dstRow = dstPixels + y * dstStride; for (int x = 0; x < buffer.width; ++x) { - // --- ⬇️ 이 부분이 최종 수정된 올바른 좌표 계산입니다 ⬇️ --- int srcX = static_cast((x + offsetX) / scale); if (srcX < 0 || srcX >= imgW) continue; - const uint8_t* srcPixel = &pixelData[(srcY * imgW + srcX) * 4]; - uint32_t dstPixelValue = dstRow[x]; uint8_t dstB = (dstPixelValue >> 0) & 0xFF; uint8_t dstG = (dstPixelValue >> 8) & 0xFF; uint8_t dstR = (dstPixelValue >> 16) & 0xFF; - uint8_t finalR = (srcPixel[0] * alphaByte + dstR * (255 - alphaByte)) / 255; uint8_t finalG = (srcPixel[1] * alphaByte + dstG * (255 - alphaByte)) / 255; uint8_t finalB = (srcPixel[2] * alphaByte + dstB * (255 - alphaByte)) / 255; - 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) { AVFormatContext* fmt_ctx = media.getFormatContext(); AVCodecContext* codec_ctx = media.getCodecContext(); @@ -443,4 +305,51 @@ void Renderer::renderVideoFrame(MediaAsset& media, ANativeWindow_Buffer& buffer, } 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(media.getWidth()) / media.getHeight(); + float surfaceAspect = static_cast(surfaceWidth) / surfaceHeight; + if (mediaAspect > surfaceAspect) { + outScale = static_cast(surfaceHeight) / media.getHeight(); + outOffsetX = (static_cast(surfaceWidth) - (media.getWidth() * outScale)) / 2.0f; + outOffsetY = 0.0f; + } else { + outScale = static_cast(surfaceWidth) / media.getWidth(); + outOffsetX = 0.0f; + outOffsetY = (static_cast(surfaceHeight) - (media.getHeight() * outScale)) / 2.0f; + } +} + +/** + * @brief 현재 렌더러의 내부 상태를 문자열로 반환합니다. (디버깅용) + */ +std::string Renderer::getDebugInfo() const { + std::stringstream ss; + ss << "========== Native State ==========\n"; + ss << " Configured Anim: " << static_cast(configuredAnimationMode_) << "\n"; + ss << " Active Anim : " << static_cast(activeAnimationMode_) << "\n"; + ss << " Configured Trans: " << static_cast(configuredTransitionMode_) << "\n"; + ss << " In CurrentState: " << static_cast(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(); } \ No newline at end of file diff --git a/app/src/main/cpp/Renderer.h b/app/src/main/cpp/Renderer.h index 38daeddc..6d64769e 100644 --- a/app/src/main/cpp/Renderer.h +++ b/app/src/main/cpp/Renderer.h @@ -49,19 +49,27 @@ public: void setPageTurnDelay(int delayMs); void setTransitionMode(int mode); - void calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight, - float& outScale, float& outOffsetX, float& outOffsetY) const; -; // --- 전략(Strategy) 객체들이 호출하는 헬퍼 함수들 --- - // (private에서 public으로 이동) - void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float offsetX, float offsetY, float scaleMultiplier); + void drawMedia(ANativeWindow_Buffer& buffer, MediaAsset& media, float alpha, float finalOffsetX, float finalOffsetY, float finalScale); 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 calculateFitScaleAndOffset(const MediaAsset& media, int surfaceWidth, int surfaceHeight, + float& outScale, float& outOffsetX, float& outOffsetY) const; 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_; MediaAsset currentMedia_; MediaAsset nextMedia_; @@ -77,16 +85,11 @@ private: float animationSpeed_ = 1.0f; // --- 상태(State) 관리 변수 --- - bool isInTransition_ = false; std::chrono::steady_clock::time_point transitionStartTime_; - bool animationCycleComplete_ = false; - bool isFirstFrameForMedia_ = true; // 새 미디어의 첫 프레임인지 확인하는 플래그 - // --- 랜덤 기능 --- std::mt19937 randomEngine_; // --- private 헬퍼 함수 --- void determineActiveAnimationMode(); - }; \ No newline at end of file diff --git a/app/src/main/cpp/native_renderer.cpp b/app/src/main/cpp/native_renderer.cpp index c515acdf..f9c54805 100644 --- a/app/src/main/cpp/native_renderer.cpp +++ b/app/src/main/cpp/native_renderer.cpp @@ -101,7 +101,10 @@ Java_bums_lunatic_launcher_wall_NativeRenderer_nativeStartNextPreload(JNIEnv* en JNIEXPORT void JNICALL Java_bums_lunatic_launcher_wall_NativeRenderer_nativeSetAnimationSpeed(JNIEnv* env, jobject, jlong nativeHandle, jfloat speed) { - // ... + Renderer* renderer = toNative(nativeHandle); + if (renderer) { + renderer->setAnimationSpeed(speed); + } } // --- ⬇️ 새로 추가할 JNI 함수들 ⬇️ --- diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt index be698a12..bf55339b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt @@ -183,9 +183,9 @@ class MyWallpaperService : WallpaperService() { nativeRenderer?.initialize() // nativeInit() -> initialize() nativeRenderer?.setFadeDuration(1500) nativeRenderer?.setTurnPageDuration(8000) - nativeRenderer?.setAnimationMode(NativeRenderer.ANIMATION_MODE_RANDOM) - nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_RANDOM) - nativeRenderer?.setAnimationSpeed(1.0f) // nativeSetAnimationSpeed -> setAnimationSpeed + nativeRenderer?.setAnimationMode(NativeRenderer.ANIMATION_MODE_PAN) + nativeRenderer?.setTransitionMode(NativeRenderer.TRANSITION_MODE_FADE) + nativeRenderer?.setAnimationSpeed(10.0f) // nativeSetAnimationSpeed -> setAnimationSpeed NativeRenderer.nativeSetNextMediaCallback(nextMediaCallback)