#include "Renderer.h" #include "AnimationStrategy.cpp" #include "AnimationStrategy.h" #include "TransitionStrategy.cpp" #include #include #include #include #include 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__) // ==================================================================== // 생성자, 소멸자 및 설정(Setter) 함수들 // ==================================================================== Renderer::Renderer() { randomEngine_.seed(std::chrono::high_resolution_clock::now().time_since_epoch().count()); setAnimationMode(static_cast(AnimationMode::PAN)); setTransitionMode(static_cast(TransitionMode::FADE)); } Renderer::~Renderer() { release(); } void Renderer::release() { std::lock_guard lock(renderMutex_); currentMedia_.release(); nextMedia_.release(); } void Renderer::setNextMedia(int fd) { preloader_.startNextPreload(fd); } void Renderer::setAnimationSpeed(float speed) { animationSpeed_ = speed > 0 ? speed : 1.0f; } void Renderer::setFadeDuration(int durationMs) { fadeDurationMs_ = durationMs > 0 ? durationMs : 3000; } void Renderer::setPageTurnDelay(int delayMs) { pageTurnDelayMs_ = delayMs > 0 ? delayMs : 5000; } void Renderer::setAnimationMode(int mode) { configuredAnimationMode_ = (mode >= 0 && mode <= static_cast(AnimationMode::PAGE_TURN)) ? static_cast(mode) : AnimationMode::PAN; determineActiveAnimationMode(); } void Renderer::setTransitionMode(int mode) { configuredTransitionMode_ = (mode >= 0 && mode <= static_cast(TransitionMode::MOSAIC)) ? static_cast(mode) : TransitionMode::FADE; } void Renderer::determineActiveAnimationMode() { AnimationMode modeToSetActive; if (configuredAnimationMode_ == AnimationMode::RANDOM) { static const std::vector availableModes = { AnimationMode::PAN, AnimationMode::ZOOM, AnimationMode::NONE, AnimationMode::PAN_ONE_WAY, AnimationMode::PAGE_TURN }; std::uniform_int_distribution dist(0, availableModes.size() - 1); modeToSetActive = availableModes[dist(randomEngine_)]; } else { modeToSetActive = configuredAnimationMode_; } activeAnimationMode_ = modeToSetActive; switch (activeAnimationMode_) { case AnimationMode::PAN: animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::PAN_ONE_WAY: animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::ZOOM: 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_); break; } } // ==================================================================== // 상태 머신(State Machine) 핸들러 함수들 // ==================================================================== void Renderer::handleAnimationState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight) { if (!animationStrategy_) return; bool isCycleComplete = animationStrategy_->execute(this, buffer, currentMedia_, surfaceWidth, surfaceHeight); if (isCycleComplete && nextMedia_.isValid()) { lastAnimationState_ = animationStrategy_->getState(); predictedNextAnimationMode_ = predictNextAnimationMode(); currentState_ = RenderState::TRANSITIONING; transitionStartTime_ = std::chrono::steady_clock::now(); TransitionMode transModeToUse = configuredTransitionMode_; if (transModeToUse == TransitionMode::RANDOM) { 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::MOSAIC) { transitionStrategy_ = std::make_unique(fadeDurationMs_, 20, 32, randomEngine_); } else { transitionStrategy_ = std::make_unique(fadeDurationMs_); } if(transitionStrategy_) transitionStrategy_->reset(); } } /** * @brief 다음에 실행될 애니메이션 모드를 미리 예측하여 반환합니다. */ Renderer::AnimationMode Renderer::predictNextAnimationMode() { if (configuredAnimationMode_ == AnimationMode::RANDOM) { static const std::vector availableModes = { AnimationMode::PAN, AnimationMode::ZOOM, AnimationMode::NONE, AnimationMode::PAN_ONE_WAY, AnimationMode::PAGE_TURN }; std::uniform_int_distribution dist(0, availableModes.size() - 1); return availableModes[dist(randomEngine_)]; } else { return configuredAnimationMode_; } } /** * @brief 주어진 애니메이션 모드의 시작 상태(좌표, 스케일)를 계산하여 반환합니다. */ AnimationState Renderer::getStartStateForMode(AnimationMode mode, int surfaceWidth, int surfaceHeight) { AnimationState startState; if (mode == AnimationMode::PAN || mode == AnimationMode::PAN_ONE_WAY) { // Pan 계열은 가장자리(0,0)에서 시작 startState.offsetX = 0.0f; startState.offsetY = 0.0f; // Pan 계열의 기본 스케일 계산 float scale; if ((static_cast(nextMedia_.getWidth()) / nextMedia_.getHeight()) > ((float)surfaceWidth / surfaceHeight)) { scale = (float)surfaceHeight / nextMedia_.getHeight(); } else { scale = (float)surfaceWidth / nextMedia_.getWidth(); } startState.scale = scale; } else { // Zoom, None, PageTurn 등은 중앙 정렬에서 시작 float baseScale, baseOffsetX, baseOffsetY; calculateFitScaleAndOffset(nextMedia_, surfaceWidth, surfaceHeight, baseScale, baseOffsetX, baseOffsetY); startState.offsetX = baseOffsetX; startState.offsetY = baseOffsetY; startState.scale = baseScale; } return startState; } void Renderer::handleTransitionState(ANativeWindow_Buffer& buffer, int surfaceWidth, int surfaceHeight) { if (!transitionStrategy_ || !nextMedia_.isValid()) { currentState_ = RenderState::ANIMATING; if(animationStrategy_) animationStrategy_->reset(); return; } // 2. 해당 모드의 전문가를 "임시로" 고용하여 시작 상태가 어떨지 물어봅니다. std::unique_ptr tempNextStrategy; switch (predictedNextAnimationMode_) { case AnimationMode::PAN: tempNextStrategy = std::make_unique(animationSpeed_); break; case AnimationMode::PAN_ONE_WAY: tempNextStrategy = std::make_unique(animationSpeed_); break; case AnimationMode::ZOOM: tempNextStrategy = std::make_unique(animationSpeed_); break; case AnimationMode::PAGE_TURN: tempNextStrategy = std::make_unique(animationSpeed_, pageTurnDelayMs_); break; case AnimationMode::NONE: default: tempNextStrategy = std::make_unique(animationSpeed_); break; } AnimationState destStartState; if (tempNextStrategy) { destStartState = tempNextStrategy->getStartState(this, nextMedia_, surfaceWidth, surfaceHeight); } // 3. 전환 전문가에게 모든 정보를 (이전 상태의 끝, 다음 상태의 시작) 넘겨주고 그리기를 위임 auto now = std::chrono::steady_clock::now(); long long elapsed = std::chrono::duration_cast(now - transitionStartTime_).count(); bool isComplete = transitionStrategy_->execute(this, buffer, currentMedia_, lastAnimationState_, nextMedia_, destStartState, elapsed); // 4. 상태 전환 체크 if (isComplete) { currentMedia_ = std::move(nextMedia_); currentState_ = RenderState::ANIMATING; // 예측했던 모드를 실제로 적용 activeAnimationMode_ = predictedNextAnimationMode_; switch (activeAnimationMode_) { // 해당 전략 객체 생성 case AnimationMode::PAN: animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::PAN_ONE_WAY: animationStrategy_ = std::make_unique(animationSpeed_); break; case AnimationMode::ZOOM: 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_); break; } 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(); } } ANativeWindow_Buffer buffer; if (ANativeWindow_lock(window, &buffer, nullptr) != 0) return; int surfaceWidth = ANativeWindow_getWidth(window); int surfaceHeight = ANativeWindow_getHeight(window); switch (currentState_) { case RenderState::ANIMATING: handleAnimationState(buffer, surfaceWidth, surfaceHeight); break; case RenderState::TRANSITIONING: handleTransitionState(buffer, surfaceWidth, surfaceHeight); break; } ANativeWindow_unlockAndPost(window); } // ==================================================================== // 하위 그리기 함수 및 헬퍼 함수들 // ==================================================================== 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 { renderVideoFrame(media, buffer, finalScale, finalOffsetX, finalOffsetY, 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; int dstStride = buffer.stride; const uint8_t* pixelData = media.getType() == MediaAsset::Type::IMAGE ? media.getImageData() : media.getRgbBuffer().data(); if (!pixelData) return; int imgW = media.getWidth(); int imgH = media.getHeight(); 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; } } } 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(); AVFrame* frame = media.getFrame(); AVPacket* pkt = media.getPacket(); SwsContext* sws_ctx = media.getSwsContext(); int video_stream_idx = media.getVideoStreamIndex(); if (!fmt_ctx || !codec_ctx || !frame || !pkt || !sws_ctx) return; int ret = av_read_frame(fmt_ctx, pkt); if (ret >= 0) { if (pkt->stream_index == video_stream_idx) { if (avcodec_send_packet(codec_ctx, pkt) >= 0) { if (avcodec_receive_frame(codec_ctx, frame) == 0) { std::vector& rgbBuf = media.getRgbBuffer(); uint8_t* dst[4] = { rgbBuf.data(), nullptr, nullptr, nullptr }; int dstStride_arr[4] = { media.getWidth() * 4, 0, 0, 0 }; sws_scale(sws_ctx, frame->data, frame->linesize, 0, media.getHeight(), dst, dstStride_arr); } } } av_packet_unref(pkt); } else if (ret == AVERROR_EOF) { av_seek_frame(fmt_ctx, video_stream_idx, 0, AVSEEK_FLAG_BACKWARD); } 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(); }