358 lines
16 KiB
C++
358 lines
16 KiB
C++
#include "Renderer.h"
|
|
#include "AnimationStrategy.cpp"
|
|
#include "AnimationStrategy.h"
|
|
#include "TransitionStrategy.cpp"
|
|
#include <android/log.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <thread>
|
|
#include <chrono>
|
|
|
|
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<int>(AnimationMode::PAN));
|
|
setTransitionMode(static_cast<int>(TransitionMode::FADE));
|
|
}
|
|
Renderer::~Renderer() { release(); }
|
|
void Renderer::release() { std::lock_guard<std::mutex> 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<int>(AnimationMode::PAGE_TURN)) ? static_cast<AnimationMode>(mode) : AnimationMode::PAN;
|
|
determineActiveAnimationMode();
|
|
}
|
|
void Renderer::setTransitionMode(int mode) {
|
|
configuredTransitionMode_ = (mode >= 0 && mode <= static_cast<int>(TransitionMode::MOSAIC)) ? static_cast<TransitionMode>(mode) : TransitionMode::FADE;
|
|
}
|
|
|
|
void Renderer::determineActiveAnimationMode() {
|
|
AnimationMode modeToSetActive;
|
|
if (configuredAnimationMode_ == AnimationMode::RANDOM) {
|
|
static const std::vector<AnimationMode> availableModes = { AnimationMode::PAN, AnimationMode::ZOOM, AnimationMode::NONE, AnimationMode::PAN_ONE_WAY, AnimationMode::PAGE_TURN };
|
|
std::uniform_int_distribution<size_t> dist(0, availableModes.size() - 1);
|
|
modeToSetActive = availableModes[dist(randomEngine_)];
|
|
} else {
|
|
modeToSetActive = configuredAnimationMode_;
|
|
}
|
|
activeAnimationMode_ = modeToSetActive;
|
|
switch (activeAnimationMode_) {
|
|
case AnimationMode::PAN: animationStrategy_ = std::make_unique<PanAnimation>(animationSpeed_); break;
|
|
case AnimationMode::PAN_ONE_WAY: animationStrategy_ = std::make_unique<PanOneWayAnimation>(animationSpeed_); break;
|
|
case AnimationMode::ZOOM: animationStrategy_ = std::make_unique<ZoomAnimation>(animationSpeed_); break;
|
|
case AnimationMode::PAGE_TURN: animationStrategy_ = std::make_unique<PageTurnAnimation>(animationSpeed_, pageTurnDelayMs_); break;
|
|
case AnimationMode::NONE: default: animationStrategy_ = std::make_unique<NoneAnimation>(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<int> dist(0, 2);
|
|
transModeToUse = static_cast<TransitionMode>(dist(randomEngine_));
|
|
}
|
|
if (transModeToUse == TransitionMode::SLIDE) {
|
|
transitionStrategy_ = std::make_unique<SlideTransition>(fadeDurationMs_, surfaceWidth);
|
|
} else if (transModeToUse == TransitionMode::MOSAIC) {
|
|
transitionStrategy_ = std::make_unique<MosaicTransition>(fadeDurationMs_, 20, 32, randomEngine_);
|
|
} else {
|
|
transitionStrategy_ = std::make_unique<FadeTransition>(fadeDurationMs_);
|
|
}
|
|
if(transitionStrategy_) transitionStrategy_->reset();
|
|
}
|
|
}
|
|
/**
|
|
* @brief 다음에 실행될 애니메이션 모드를 미리 예측하여 반환합니다.
|
|
*/
|
|
Renderer::AnimationMode Renderer::predictNextAnimationMode() {
|
|
if (configuredAnimationMode_ == AnimationMode::RANDOM) {
|
|
static const std::vector<AnimationMode> availableModes = {
|
|
AnimationMode::PAN, AnimationMode::ZOOM, AnimationMode::NONE,
|
|
AnimationMode::PAN_ONE_WAY, AnimationMode::PAGE_TURN
|
|
};
|
|
std::uniform_int_distribution<size_t> 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<float>(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<AnimationStrategy> tempNextStrategy;
|
|
switch (predictedNextAnimationMode_) {
|
|
case AnimationMode::PAN:
|
|
tempNextStrategy = std::make_unique<PanAnimation>(animationSpeed_);
|
|
break;
|
|
case AnimationMode::PAN_ONE_WAY:
|
|
tempNextStrategy = std::make_unique<PanOneWayAnimation>(animationSpeed_);
|
|
break;
|
|
case AnimationMode::ZOOM:
|
|
tempNextStrategy = std::make_unique<ZoomAnimation>(animationSpeed_);
|
|
break;
|
|
case AnimationMode::PAGE_TURN:
|
|
tempNextStrategy = std::make_unique<PageTurnAnimation>(animationSpeed_, pageTurnDelayMs_);
|
|
break;
|
|
case AnimationMode::NONE:
|
|
default:
|
|
tempNextStrategy = std::make_unique<NoneAnimation>(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<std::chrono::milliseconds>(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<PanAnimation>(animationSpeed_); break;
|
|
case AnimationMode::PAN_ONE_WAY: animationStrategy_ = std::make_unique<PanOneWayAnimation>(animationSpeed_); break;
|
|
case AnimationMode::ZOOM: animationStrategy_ = std::make_unique<ZoomAnimation>(animationSpeed_); break;
|
|
case AnimationMode::PAGE_TURN: animationStrategy_ = std::make_unique<PageTurnAnimation>(animationSpeed_, pageTurnDelayMs_); break;
|
|
case AnimationMode::NONE:
|
|
default: animationStrategy_ = std::make_unique<NoneAnimation>(animationSpeed_);
|
|
break;
|
|
}
|
|
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(); }
|
|
}
|
|
|
|
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<uint8_t>(alpha * 255.0f);
|
|
|
|
for (int y = 0; y < buffer.height; ++y) {
|
|
int srcY = static_cast<int>((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<int>((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<uint8_t>& 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<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();
|
|
} |