diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15de58d..7b5d39c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,17 @@ on: branches: [main] jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Check clang-format + uses: jidicula/clang-format-action@v4.14.0 + with: + clang-format-version: '21' + check-path: 'src' + test: runs-on: ubuntu-latest steps: diff --git a/src/fleximg/core/affine_capability.h b/src/fleximg/core/affine_capability.h index 52c5f27..8e6698b 100644 --- a/src/fleximg/core/affine_capability.h +++ b/src/fleximg/core/affine_capability.h @@ -27,69 +27,79 @@ namespace FLEXIMG_NAMESPACE { class AffineCapability { public: - AffineCapability() = default; - virtual ~AffineCapability() = default; - - // ======================================== - // 行列アクセサ - // ======================================== - - void setMatrix(const AffineMatrix &m) { localMatrix_ = m; } - const AffineMatrix &matrix() const { return localMatrix_; } - - // ======================================== - // 便利なセッター(AffineNode と同一API) - // ======================================== - - // 回転を設定(a,b,c,d のみ変更、tx,ty は維持) - void setRotation(float radians) { - float c = std::cos(radians); - float s = std::sin(radians); - localMatrix_.a = c; - localMatrix_.b = -s; - localMatrix_.c = s; - localMatrix_.d = c; - } - - // スケールを設定(a,b,c,d のみ変更、tx,ty は維持) - void setScale(float sx, float sy) { - localMatrix_.a = sx; - localMatrix_.b = 0; - localMatrix_.c = 0; - localMatrix_.d = sy; - } - - // 平行移動を設定(tx,ty のみ変更、a,b,c,d は維持) - void setTranslation(float tx, float ty) { - localMatrix_.tx = tx; - localMatrix_.ty = ty; - } - - // 回転+スケールを設定(a,b,c,d のみ変更、tx,ty は維持) - void setRotationScale(float radians, float sx, float sy) { - float c = std::cos(radians); - float s = std::sin(radians); - localMatrix_.a = c * sx; - localMatrix_.b = -s * sy; - localMatrix_.c = s * sx; - localMatrix_.d = c * sy; - } - - // ======================================== - // ユーティリティ - // ======================================== - - // ローカル変換が設定されているか(単位行列でないか) - bool hasLocalTransform() const { - return localMatrix_.a != 1.0f || localMatrix_.b != 0.0f || - localMatrix_.c != 0.0f || localMatrix_.d != 1.0f || - localMatrix_.tx != 0.0f || localMatrix_.ty != 0.0f; - } + AffineCapability() = default; + virtual ~AffineCapability() = default; + + // ======================================== + // 行列アクセサ + // ======================================== + + void setMatrix(const AffineMatrix &m) + { + localMatrix_ = m; + } + const AffineMatrix &matrix() const + { + return localMatrix_; + } + + // ======================================== + // 便利なセッター(AffineNode と同一API) + // ======================================== + + // 回転を設定(a,b,c,d のみ変更、tx,ty は維持) + void setRotation(float radians) + { + float c = std::cos(radians); + float s = std::sin(radians); + localMatrix_.a = c; + localMatrix_.b = -s; + localMatrix_.c = s; + localMatrix_.d = c; + } + + // スケールを設定(a,b,c,d のみ変更、tx,ty は維持) + void setScale(float sx, float sy) + { + localMatrix_.a = sx; + localMatrix_.b = 0; + localMatrix_.c = 0; + localMatrix_.d = sy; + } + + // 平行移動を設定(tx,ty のみ変更、a,b,c,d は維持) + void setTranslation(float tx, float ty) + { + localMatrix_.tx = tx; + localMatrix_.ty = ty; + } + + // 回転+スケールを設定(a,b,c,d のみ変更、tx,ty は維持) + void setRotationScale(float radians, float sx, float sy) + { + float c = std::cos(radians); + float s = std::sin(radians); + localMatrix_.a = c * sx; + localMatrix_.b = -s * sy; + localMatrix_.c = s * sx; + localMatrix_.d = c * sy; + } + + // ======================================== + // ユーティリティ + // ======================================== + + // ローカル変換が設定されているか(単位行列でないか) + bool hasLocalTransform() const + { + return localMatrix_.a != 1.0f || localMatrix_.b != 0.0f || localMatrix_.c != 0.0f || localMatrix_.d != 1.0f || + localMatrix_.tx != 0.0f || localMatrix_.ty != 0.0f; + } protected: - AffineMatrix localMatrix_; // ローカル変換行列(デフォルトは単位行列) + AffineMatrix localMatrix_; // ローカル変換行列(デフォルトは単位行列) }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_AFFINE_CAPABILITY_H +#endif // FLEXIMG_AFFINE_CAPABILITY_H diff --git a/src/fleximg/core/common.h b/src/fleximg/core/common.h index d41574c..ef26f79 100644 --- a/src/fleximg/core/common.h +++ b/src/fleximg/core/common.h @@ -13,8 +13,8 @@ #include "types.h" -#include // printf -#include // std::abort +#include // printf +#include // std::abort // ======================================================================== // デバッグログマクロ @@ -31,18 +31,18 @@ // #ifdef ARDUINO -#define FLEXIMG_DEBUG_LOG(fmt, ...) \ - do { \ - printf(fmt "\n", ##__VA_ARGS__); \ - fflush(stdout); \ - vTaskDelay(1); \ - } while (0) +#define FLEXIMG_DEBUG_LOG(fmt, ...) \ + do { \ + printf(fmt "\n", ##__VA_ARGS__); \ + fflush(stdout); \ + vTaskDelay(1); \ + } while (0) #else -#define FLEXIMG_DEBUG_LOG(fmt, ...) \ - do { \ - printf(fmt "\n", ##__VA_ARGS__); \ - fflush(stdout); \ - } while (0) +#define FLEXIMG_DEBUG_LOG(fmt, ...) \ + do { \ + printf(fmt "\n", ##__VA_ARGS__); \ + fflush(stdout); \ + } while (0) #endif #ifdef FLEXIMG_DEBUG @@ -60,24 +60,24 @@ // #ifdef FLEXIMG_DEBUG -#define FLEXIMG_ASSERT(cond, msg) \ - do { \ - if (!(cond)) { \ - FLEXIMG_DEBUG_LOG("ASSERT FAIL: %s", msg); \ - std::abort(); \ - } \ - } while (0) +#define FLEXIMG_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + FLEXIMG_DEBUG_LOG("ASSERT FAIL: %s", msg); \ + std::abort(); \ + } \ + } while (0) #else #define FLEXIMG_ASSERT(cond, msg) ((void)0) #endif -#define FLEXIMG_REQUIRE(cond, msg) \ - do { \ - if (!(cond)) { \ - FLEXIMG_DEBUG_LOG("REQUIRE FAIL: %s", msg); \ - std::abort(); \ - } \ - } while (0) +#define FLEXIMG_REQUIRE(cond, msg) \ + do { \ + if (!(cond)) { \ + FLEXIMG_DEBUG_LOG("REQUIRE FAIL: %s", msg); \ + std::abort(); \ + } \ + } while (0) // ======================================================================== // Deprecated attribute @@ -94,4 +94,4 @@ #define FLEXIMG_VERSION_MINOR 0 #define FLEXIMG_VERSION_PATCH 0 -#endif // FLEXIMG_COMMON_H +#endif // FLEXIMG_COMMON_H diff --git a/src/fleximg/core/data_range_cache.h b/src/fleximg/core/data_range_cache.h index 92305a5..8c36ea0 100644 --- a/src/fleximg/core/data_range_cache.h +++ b/src/fleximg/core/data_range_cache.h @@ -41,48 +41,56 @@ namespace core { class DataRangeCache { public: - DataRangeCache() = default; + DataRangeCache() = default; - /// @brief キャッシュから取得を試みる - /// @param request リクエスト(キー) - /// @param out 取得結果の格納先 - /// @return true=キャッシュヒット, false=キャッシュミス - bool tryGet(const RenderRequest &request, DataRange &out) const { - if (!valid_) { - return false; + /// @brief キャッシュから取得を試みる + /// @param request リクエスト(キー) + /// @param out 取得結果の格納先 + /// @return true=キャッシュヒット, false=キャッシュミス + bool tryGet(const RenderRequest &request, DataRange &out) const + { + if (!valid_) { + return false; + } + if (cachedOrigin_.x != request.origin.x || cachedOrigin_.y != request.origin.y || + cachedWidth_ != request.width) { + return false; + } + out = cachedRange_; + return true; } - if (cachedOrigin_.x != request.origin.x || - cachedOrigin_.y != request.origin.y || cachedWidth_ != request.width) { - return false; - } - out = cachedRange_; - return true; - } - /// @brief キャッシュに設定 - /// @param request リクエスト(キー) - /// @param range 範囲(値) - void set(const RenderRequest &request, const DataRange &range) { - cachedOrigin_ = request.origin; - cachedWidth_ = request.width; - cachedRange_ = range; - valid_ = true; - } + /// @brief キャッシュに設定 + /// @param request リクエスト(キー) + /// @param range 範囲(値) + void set(const RenderRequest &request, const DataRange &range) + { + cachedOrigin_ = request.origin; + cachedWidth_ = request.width; + cachedRange_ = range; + valid_ = true; + } - /// @brief キャッシュ無効化(Prepare時に呼び出し) - void invalidate() { valid_ = false; } + /// @brief キャッシュ無効化(Prepare時に呼び出し) + void invalidate() + { + valid_ = false; + } - /// @brief キャッシュが有効か問い合わせ(テスト/デバッグ用) - bool isValid() const { return valid_; } + /// @brief キャッシュが有効か問い合わせ(テスト/デバッグ用) + bool isValid() const + { + return valid_; + } private: - Point cachedOrigin_ = {0, 0}; - int16_t cachedWidth_ = 0; - DataRange cachedRange_ = {0, 0}; - bool valid_ = false; + Point cachedOrigin_ = {0, 0}; + int16_t cachedWidth_ = 0; + DataRange cachedRange_ = {0, 0}; + bool valid_ = false; }; -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_DATA_RANGE_CACHE_H +#endif // FLEXIMG_DATA_RANGE_CACHE_H diff --git a/src/fleximg/core/format_metrics.h b/src/fleximg/core/format_metrics.h index 847e422..9efc6c3 100644 --- a/src/fleximg/core/format_metrics.h +++ b/src/fleximg/core/format_metrics.h @@ -2,7 +2,7 @@ #define FLEXIMG_FORMAT_METRICS_H #include "common.h" -#include "perf_metrics.h" // FLEXIMG_DEBUG_PERF_METRICS マクロ +#include "perf_metrics.h" // FLEXIMG_DEBUG_PERF_METRICS マクロ #include namespace FLEXIMG_NAMESPACE { @@ -37,31 +37,29 @@ namespace core { namespace FormatIdx { constexpr uint_fast8_t RGBA8_Straight = 0; -constexpr uint_fast8_t RGB565_LE = 1; -constexpr uint_fast8_t RGB565_BE = 2; -constexpr uint_fast8_t RGB332 = 3; -constexpr uint_fast8_t RGB888 = 4; -constexpr uint_fast8_t BGR888 = 5; -constexpr uint_fast8_t Alpha8 = 6; -constexpr uint_fast8_t Grayscale8 = 7; -constexpr uint_fast8_t GrayscaleN = - 7; // bit-packed Grayscale → Grayscale8 と共有 -constexpr uint_fast8_t Index8 = 8; -constexpr uint_fast8_t IndexN = 8; // bit-packed Index → Index8 と共有 -constexpr uint_fast8_t Count = 9; -} // namespace FormatIdx +constexpr uint_fast8_t RGB565_LE = 1; +constexpr uint_fast8_t RGB565_BE = 2; +constexpr uint_fast8_t RGB332 = 3; +constexpr uint_fast8_t RGB888 = 4; +constexpr uint_fast8_t BGR888 = 5; +constexpr uint_fast8_t Alpha8 = 6; +constexpr uint_fast8_t Grayscale8 = 7; +constexpr uint_fast8_t GrayscaleN = 7; // bit-packed Grayscale → Grayscale8 と共有 +constexpr uint_fast8_t Index8 = 8; +constexpr uint_fast8_t IndexN = 8; // bit-packed Index → Index8 と共有 +constexpr uint_fast8_t Count = 9; +} // namespace FormatIdx // ======================================================================== // 操作タイプ // ======================================================================== namespace OpType { -constexpr uint_fast8_t ToStraight = 0; // 各フォーマット → RGBA8_Straight -constexpr uint_fast8_t FromStraight = 1; // RGBA8_Straight → 各フォーマット -constexpr uint_fast8_t BlendUnder = - 2; // 各フォーマット → Straight dst (under合成) -constexpr uint_fast8_t Count = 3; -} // namespace OpType +constexpr uint_fast8_t ToStraight = 0; // 各フォーマット → RGBA8_Straight +constexpr uint_fast8_t FromStraight = 1; // RGBA8_Straight → 各フォーマット +constexpr uint_fast8_t BlendUnder = 2; // 各フォーマット → Straight dst (under合成) +constexpr uint_fast8_t Count = 3; +} // namespace OpType // ======================================================================== // メトリクス構造体 @@ -70,140 +68,167 @@ constexpr uint_fast8_t Count = 3; #ifdef FLEXIMG_DEBUG_PERF_METRICS struct FormatOpEntry { - uint32_t callCount = 0; // 呼び出し回数 - uint64_t pixelCount = 0; // 処理ピクセル数 - - void reset() { - callCount = 0; - pixelCount = 0; - } - - void record(size_t pixels) { - callCount++; - pixelCount += static_cast(pixels); - } + uint32_t callCount = 0; // 呼び出し回数 + uint64_t pixelCount = 0; // 処理ピクセル数 + + void reset() + { + callCount = 0; + pixelCount = 0; + } + + void record(size_t pixels) + { + callCount++; + pixelCount += static_cast(pixels); + } }; struct FormatMetrics { - FormatOpEntry data[FormatIdx::Count][OpType::Count]; - - // シングルトンインスタンス - static FormatMetrics &instance() { - static FormatMetrics s_instance; - return s_instance; - } - - void reset() { - for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { - for (uint_fast8_t o = 0; o < OpType::Count; ++o) { - data[f][o].reset(); - } - } - } - - void record(uint_fast8_t formatIdx, uint_fast8_t opType, size_t pixels) { - if (formatIdx < FormatIdx::Count && opType < OpType::Count) { - data[formatIdx][opType].record(pixels); - } - } - - // 全フォーマットの合計(操作タイプ別) - FormatOpEntry totalByOp(uint_fast8_t opType) const { - FormatOpEntry total; - if (opType < OpType::Count) { - for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { - total.callCount += data[f][opType].callCount; - total.pixelCount += data[f][opType].pixelCount; - } - } - return total; - } - - // 全操作の合計(フォーマット別) - FormatOpEntry totalByFormat(uint_fast8_t formatIdx) const { - FormatOpEntry total; - if (formatIdx < FormatIdx::Count) { - for (uint_fast8_t o = 0; o < OpType::Count; ++o) { - total.callCount += data[formatIdx][o].callCount; - total.pixelCount += data[formatIdx][o].pixelCount; - } - } - return total; - } - - // 全体合計 - FormatOpEntry total() const { - FormatOpEntry t; - for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { - for (uint_fast8_t o = 0; o < OpType::Count; ++o) { - t.callCount += data[f][o].callCount; - t.pixelCount += data[f][o].pixelCount; - } - } - return t; - } - - // スナップショット(現在の状態を保存) - void - saveSnapshot(FormatOpEntry snapshot[FormatIdx::Count][OpType::Count]) const { - for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { - for (uint_fast8_t o = 0; o < OpType::Count; ++o) { - snapshot[f][o] = data[f][o]; - } - } - } - - // スナップショットから復元 - void restoreSnapshot( - const FormatOpEntry snapshot[FormatIdx::Count][OpType::Count]) { - for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { - for (uint_fast8_t o = 0; o < OpType::Count; ++o) { - data[f][o] = snapshot[f][o]; - } - } - } + FormatOpEntry data[FormatIdx::Count][OpType::Count]; + + // シングルトンインスタンス + static FormatMetrics &instance() + { + static FormatMetrics s_instance; + return s_instance; + } + + void reset() + { + for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { + for (uint_fast8_t o = 0; o < OpType::Count; ++o) { + data[f][o].reset(); + } + } + } + + void record(uint_fast8_t formatIdx, uint_fast8_t opType, size_t pixels) + { + if (formatIdx < FormatIdx::Count && opType < OpType::Count) { + data[formatIdx][opType].record(pixels); + } + } + + // 全フォーマットの合計(操作タイプ別) + FormatOpEntry totalByOp(uint_fast8_t opType) const + { + FormatOpEntry total; + if (opType < OpType::Count) { + for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { + total.callCount += data[f][opType].callCount; + total.pixelCount += data[f][opType].pixelCount; + } + } + return total; + } + + // 全操作の合計(フォーマット別) + FormatOpEntry totalByFormat(uint_fast8_t formatIdx) const + { + FormatOpEntry total; + if (formatIdx < FormatIdx::Count) { + for (uint_fast8_t o = 0; o < OpType::Count; ++o) { + total.callCount += data[formatIdx][o].callCount; + total.pixelCount += data[formatIdx][o].pixelCount; + } + } + return total; + } + + // 全体合計 + FormatOpEntry total() const + { + FormatOpEntry t; + for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { + for (uint_fast8_t o = 0; o < OpType::Count; ++o) { + t.callCount += data[f][o].callCount; + t.pixelCount += data[f][o].pixelCount; + } + } + return t; + } + + // スナップショット(現在の状態を保存) + void saveSnapshot(FormatOpEntry snapshot[FormatIdx::Count][OpType::Count]) const + { + for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { + for (uint_fast8_t o = 0; o < OpType::Count; ++o) { + snapshot[f][o] = data[f][o]; + } + } + } + + // スナップショットから復元 + void restoreSnapshot(const FormatOpEntry snapshot[FormatIdx::Count][OpType::Count]) + { + for (uint_fast8_t f = 0; f < FormatIdx::Count; ++f) { + for (uint_fast8_t o = 0; o < OpType::Count; ++o) { + data[f][o] = snapshot[f][o]; + } + } + } }; // 計測マクロ -#define FLEXIMG_FMT_METRICS(fmt, op, pixels) \ - ::FLEXIMG_NAMESPACE::core::FormatMetrics::instance().record( \ - ::FLEXIMG_NAMESPACE::core::FormatIdx::fmt, \ - ::FLEXIMG_NAMESPACE::core::OpType::op, pixels) +#define FLEXIMG_FMT_METRICS(fmt, op, pixels) \ + ::FLEXIMG_NAMESPACE::core::FormatMetrics::instance().record(::FLEXIMG_NAMESPACE::core::FormatIdx::fmt, \ + ::FLEXIMG_NAMESPACE::core::OpType::op, pixels) #else // リリースビルド用のダミー構造体 struct FormatOpEntry { - void reset() {} + void reset() + { + } }; struct FormatMetrics { - static FormatMetrics &instance() { - static FormatMetrics s_instance; - return s_instance; - } - void reset() {} - void record(uint_fast8_t, uint_fast8_t, size_t) {} - FormatOpEntry totalByOp(uint_fast8_t) const { return FormatOpEntry{}; } - FormatOpEntry totalByFormat(uint_fast8_t) const { return FormatOpEntry{}; } - FormatOpEntry total() const { return FormatOpEntry{}; } - void saveSnapshot(FormatOpEntry[FormatIdx::Count][OpType::Count]) const {} - void restoreSnapshot(const FormatOpEntry[FormatIdx::Count][OpType::Count]) {} + static FormatMetrics &instance() + { + static FormatMetrics s_instance; + return s_instance; + } + void reset() + { + } + void record(uint_fast8_t, uint_fast8_t, size_t) + { + } + FormatOpEntry totalByOp(uint_fast8_t) const + { + return FormatOpEntry{}; + } + FormatOpEntry totalByFormat(uint_fast8_t) const + { + return FormatOpEntry{}; + } + FormatOpEntry total() const + { + return FormatOpEntry{}; + } + void saveSnapshot(FormatOpEntry[FormatIdx::Count][OpType::Count]) const + { + } + void restoreSnapshot(const FormatOpEntry[FormatIdx::Count][OpType::Count]) + { + } }; // リリースビルド用: メトリクス計測マクロは何もしない #define FLEXIMG_FMT_METRICS(fmt, op, pixels) ((void)0) -#endif // FLEXIMG_DEBUG_PERF_METRICS +#endif // FLEXIMG_DEBUG_PERF_METRICS -} // namespace core +} // namespace core // 親名前空間に公開 namespace FormatIdx = core::FormatIdx; -namespace OpType = core::OpType; +namespace OpType = core::OpType; using core::FormatMetrics; using core::FormatOpEntry; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_FORMAT_METRICS_H +#endif // FLEXIMG_FORMAT_METRICS_H diff --git a/src/fleximg/core/memory/allocator.h b/src/fleximg/core/memory/allocator.h index 62cfacd..3de12f5 100644 --- a/src/fleximg/core/memory/allocator.h +++ b/src/fleximg/core/memory/allocator.h @@ -27,20 +27,20 @@ namespace memory { class IAllocator { public: - virtual ~IAllocator() = default; + virtual ~IAllocator() = default; - /// @brief メモリを確保 - /// @param bytes 確保するサイズ(バイト) - /// @param alignment アライメント(デフォルト16バイト) - /// @return 確保したメモリへのポインタ(失敗時はnullptr) - virtual void *allocate(size_t bytes, size_t alignment = 16) = 0; + /// @brief メモリを確保 + /// @param bytes 確保するサイズ(バイト) + /// @param alignment アライメント(デフォルト16バイト) + /// @return 確保したメモリへのポインタ(失敗時はnullptr) + virtual void *allocate(size_t bytes, size_t alignment = 16) = 0; - /// @brief メモリを解放 - /// @param ptr 解放するメモリのポインタ - virtual void deallocate(void *ptr) = 0; + /// @brief メモリを解放 + /// @param ptr 解放するメモリのポインタ + virtual void deallocate(void *ptr) = 0; - /// @brief アロケータ名を取得(デバッグ用) - virtual const char *name() const = 0; + /// @brief アロケータ名を取得(デバッグ用) + virtual const char *name() const = 0; }; // ======================================================================== @@ -50,58 +50,62 @@ class IAllocator { class DefaultAllocator : public IAllocator { public: #ifdef FLEXIMG_TRAP_DEFAULT_ALLOCATOR - // デバッグ用: トラップ有効フラグ - static bool &trapEnabled() { - static bool enabled = false; - return enabled; - } + // デバッグ用: トラップ有効フラグ + static bool &trapEnabled() + { + static bool enabled = false; + return enabled; + } #endif - void *allocate(size_t bytes, size_t alignment = 16) override { + void *allocate(size_t bytes, size_t alignment = 16) override + { #ifdef FLEXIMG_TRAP_DEFAULT_ALLOCATOR - // デバッグ用: トラップ有効時にDefaultAllocatorが使われたら停止 - if (trapEnabled()) { - assert( - false && - "DefaultAllocator::allocate() called - use backtrace to find caller"); - } + // デバッグ用: トラップ有効時にDefaultAllocatorが使われたら停止 + if (trapEnabled()) { + assert(false && "DefaultAllocator::allocate() called - use backtrace to find caller"); + } #endif #ifdef _WIN32 - return _aligned_malloc(bytes, alignment); + return _aligned_malloc(bytes, alignment); #else - void *ptr = nullptr; - // posix_memalignはalignmentがsizeof(void*)の倍数である必要がある - if (alignment < sizeof(void *)) { - alignment = sizeof(void *); - } - if (posix_memalign(&ptr, alignment, bytes) != 0) { - return nullptr; - } - return ptr; + void *ptr = nullptr; + // posix_memalignはalignmentがsizeof(void*)の倍数である必要がある + if (alignment < sizeof(void *)) { + alignment = sizeof(void *); + } + if (posix_memalign(&ptr, alignment, bytes) != 0) { + return nullptr; + } + return ptr; #endif - } + } - void deallocate(void *ptr) override { - if (!ptr) - return; + void deallocate(void *ptr) override + { + if (!ptr) return; #ifdef _WIN32 - _aligned_free(ptr); + _aligned_free(ptr); #else - free(ptr); + free(ptr); #endif - } + } - const char *name() const override { return "DefaultAllocator"; } + const char *name() const override + { + return "DefaultAllocator"; + } - /// @brief シングルトンインスタンス取得 - static DefaultAllocator &instance() { - static DefaultAllocator s_instance; - return s_instance; - } + /// @brief シングルトンインスタンス取得 + static DefaultAllocator &instance() + { + static DefaultAllocator s_instance; + return s_instance; + } }; -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_CORE_MEMORY_ALLOCATOR_H +#endif // FLEXIMG_CORE_MEMORY_ALLOCATOR_H diff --git a/src/fleximg/core/memory/buffer_handle.h b/src/fleximg/core/memory/buffer_handle.h index 17c5bdd..912d179 100644 --- a/src/fleximg/core/memory/buffer_handle.h +++ b/src/fleximg/core/memory/buffer_handle.h @@ -24,80 +24,98 @@ namespace memory { class BufferHandle { public: - /// @brief デフォルトコンストラクタ - BufferHandle() = default; - - /// @brief サイズ指定コンストラクタ - /// @param size 確保するサイズ - /// @param options 確保オプション - explicit BufferHandle(size_t size, const AllocateOptions &options = {}) - : size_(size) { - ptr_ = getPlatformMemory().allocate(size, options); - if (!ptr_) { - size_ = 0; + /// @brief デフォルトコンストラクタ + BufferHandle() = default; + + /// @brief サイズ指定コンストラクタ + /// @param size 確保するサイズ + /// @param options 確保オプション + explicit BufferHandle(size_t size, const AllocateOptions &options = {}) : size_(size) + { + ptr_ = getPlatformMemory().allocate(size, options); + if (!ptr_) { + size_ = 0; + } } - } - - /// @brief デストラクタ(自動解放) - ~BufferHandle() { reset(); } - - /// @brief ムーブコンストラクタ - BufferHandle(BufferHandle &&other) noexcept - : ptr_(other.ptr_), size_(other.size_) { - other.ptr_ = nullptr; - other.size_ = 0; - } - - /// @brief ムーブ代入演算子 - BufferHandle &operator=(BufferHandle &&other) noexcept { - if (this != &other) { - reset(); - ptr_ = other.ptr_; - size_ = other.size_; - other.ptr_ = nullptr; - other.size_ = 0; + + /// @brief デストラクタ(自動解放) + ~BufferHandle() + { + reset(); + } + + /// @brief ムーブコンストラクタ + BufferHandle(BufferHandle &&other) noexcept : ptr_(other.ptr_), size_(other.size_) + { + other.ptr_ = nullptr; + other.size_ = 0; + } + + /// @brief ムーブ代入演算子 + BufferHandle &operator=(BufferHandle &&other) noexcept + { + if (this != &other) { + reset(); + ptr_ = other.ptr_; + size_ = other.size_; + other.ptr_ = nullptr; + other.size_ = 0; + } + return *this; } - return *this; - } - /// @brief コピー禁止 - BufferHandle(const BufferHandle &) = delete; - BufferHandle &operator=(const BufferHandle &) = delete; + /// @brief コピー禁止 + BufferHandle(const BufferHandle &) = delete; + BufferHandle &operator=(const BufferHandle &) = delete; - /// @brief データポインタ取得 - void *data() { return ptr_; } - const void *data() const { return ptr_; } + /// @brief データポインタ取得 + void *data() + { + return ptr_; + } + const void *data() const + { + return ptr_; + } - /// @brief サイズ取得 - size_t size() const { return size_; } + /// @brief サイズ取得 + size_t size() const + { + return size_; + } - /// @brief 有効性チェック - explicit operator bool() const { return ptr_ != nullptr; } + /// @brief 有効性チェック + explicit operator bool() const + { + return ptr_ != nullptr; + } - /// @brief リセット(解放) - void reset() { - if (ptr_) { - getPlatformMemory().deallocate(ptr_); - ptr_ = nullptr; - size_ = 0; + /// @brief リセット(解放) + void reset() + { + if (ptr_) { + getPlatformMemory().deallocate(ptr_); + ptr_ = nullptr; + size_ = 0; + } } - } - /// @brief 所有権の放棄(解放されなくなる) - void *release() { - void *p = ptr_; - ptr_ = nullptr; - size_ = 0; - return p; - } + /// @brief 所有権の放棄(解放されなくなる) + void *release() + { + void *p = ptr_; + ptr_ = nullptr; + size_ = 0; + return p; + } private: - void *ptr_ = nullptr; - size_t size_ = 0; + void *ptr_ = nullptr; + size_t size_ = 0; }; -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_CORE_MEMORY_BUFFER_HANDLE_H +#endif // FLEXIMG_CORE_MEMORY_BUFFER_HANDLE_H diff --git a/src/fleximg/core/memory/platform.h b/src/fleximg/core/memory/platform.h index 7d550af..a3cde43 100644 --- a/src/fleximg/core/memory/platform.h +++ b/src/fleximg/core/memory/platform.h @@ -23,9 +23,9 @@ namespace memory { // ======================================================================== enum class MemorySpeed { - Fast, // 高速メモリ優先(SRAM) - Normal, // 通常メモリ(SRAM推奨だがPSRAMも可) - Slow, // 低速メモリ可(PSRAM可、大容量優先) + Fast, // 高速メモリ優先(SRAM) + Normal, // 通常メモリ(SRAM推奨だがPSRAMも可) + Slow, // 低速メモリ可(PSRAM可、大容量優先) }; // ======================================================================== @@ -33,9 +33,9 @@ enum class MemorySpeed { // ======================================================================== enum class MemoryFallback { - NoFallback, // フォールバックなし、失敗時はnullptrを返す - AllowPSRAM, // PSRAM使用を許可 - AllowAny, // 任意のメモリ使用を許可 + NoFallback, // フォールバックなし、失敗時はnullptrを返す + AllowPSRAM, // PSRAM使用を許可 + AllowAny, // 任意のメモリ使用を許可 }; // ======================================================================== @@ -43,9 +43,9 @@ enum class MemoryFallback { // ======================================================================== struct AllocateOptions { - MemorySpeed speed = MemorySpeed::Normal; - MemoryFallback fallback = MemoryFallback::AllowAny; - size_t alignment = 16; // デフォルト16バイトアライメント + MemorySpeed speed = MemorySpeed::Normal; + MemoryFallback fallback = MemoryFallback::AllowAny; + size_t alignment = 16; // デフォルト16バイトアライメント }; // ======================================================================== @@ -54,23 +54,23 @@ struct AllocateOptions { class IPlatformMemory { public: - virtual ~IPlatformMemory() = default; + virtual ~IPlatformMemory() = default; - /// @brief メモリを確保 - /// @param size 確保するサイズ(バイト) - /// @param options 確保オプション - /// @return 確保したメモリへのポインタ(失敗時はnullptr) - virtual void *allocate(size_t size, const AllocateOptions &options) = 0; + /// @brief メモリを確保 + /// @param size 確保するサイズ(バイト) + /// @param options 確保オプション + /// @return 確保したメモリへのポインタ(失敗時はnullptr) + virtual void *allocate(size_t size, const AllocateOptions &options) = 0; - /// @brief メモリを解放 - /// @param ptr 解放するメモリのポインタ - virtual void deallocate(void *ptr) = 0; + /// @brief メモリを解放 + /// @param ptr 解放するメモリのポインタ + virtual void deallocate(void *ptr) = 0; - /// @brief PSRAM が利用可能か - virtual bool hasPSRAM() const = 0; + /// @brief PSRAM が利用可能か + virtual bool hasPSRAM() const = 0; - /// @brief 指定したポインタがPSRAM上にあるか - virtual bool isPSRAM(void *ptr) const = 0; + /// @brief 指定したポインタがPSRAM上にあるか + virtual bool isPSRAM(void *ptr) const = 0; }; // ======================================================================== @@ -89,20 +89,27 @@ void setPlatformMemory(IPlatformMemory *platformMemory); class DefaultPlatformMemory : public IPlatformMemory { public: - void *allocate(size_t size, const AllocateOptions &options) override; - void deallocate(void *ptr) override; - bool hasPSRAM() const override { return false; } - bool isPSRAM(void * /*ptr*/) const override { return false; } - - static DefaultPlatformMemory &instance() { - static DefaultPlatformMemory s_instance; - return s_instance; - } + void *allocate(size_t size, const AllocateOptions &options) override; + void deallocate(void *ptr) override; + bool hasPSRAM() const override + { + return false; + } + bool isPSRAM(void * /*ptr*/) const override + { + return false; + } + + static DefaultPlatformMemory &instance() + { + static DefaultPlatformMemory s_instance; + return s_instance; + } }; -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -118,31 +125,34 @@ namespace memory { // グローバルプラットフォームメモリインスタンス static IPlatformMemory *s_platformMemory = nullptr; -IPlatformMemory &getPlatformMemory() { - if (!s_platformMemory) { - s_platformMemory = &DefaultPlatformMemory::instance(); - } - return *s_platformMemory; +IPlatformMemory &getPlatformMemory() +{ + if (!s_platformMemory) { + s_platformMemory = &DefaultPlatformMemory::instance(); + } + return *s_platformMemory; } -void setPlatformMemory(IPlatformMemory *platformMemory) { - s_platformMemory = platformMemory; +void setPlatformMemory(IPlatformMemory *platformMemory) +{ + s_platformMemory = platformMemory; } // DefaultPlatformMemory の実装 -void *DefaultPlatformMemory::allocate(size_t size, - const AllocateOptions &options) { - return DefaultAllocator::instance().allocate(size, options.alignment); +void *DefaultPlatformMemory::allocate(size_t size, const AllocateOptions &options) +{ + return DefaultAllocator::instance().allocate(size, options.alignment); } -void DefaultPlatformMemory::deallocate(void *ptr) { - DefaultAllocator::instance().deallocate(ptr); +void DefaultPlatformMemory::deallocate(void *ptr) +{ + DefaultAllocator::instance().deallocate(ptr); } -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_CORE_MEMORY_PLATFORM_H +#endif // FLEXIMG_CORE_MEMORY_PLATFORM_H diff --git a/src/fleximg/core/memory/pool_allocator.h b/src/fleximg/core/memory/pool_allocator.h index f222366..d918f64 100644 --- a/src/fleximg/core/memory/pool_allocator.h +++ b/src/fleximg/core/memory/pool_allocator.h @@ -25,21 +25,22 @@ namespace memory { #ifdef FLEXIMG_DEBUG_PERF_METRICS struct PoolStats { - size_t totalAllocations = 0; // 累計確保回数 - size_t totalDeallocations = 0; // 累計解放回数 - size_t hits = 0; // 確保成功回数 - size_t misses = 0; // 確保失敗回数 - size_t peakUsedBlocks = 0; // 最大同時使用ブロック数 - uint32_t allocatedBitmap = 0; // 現在の使用状況(デバッグ用) - - void reset() { - totalAllocations = 0; - totalDeallocations = 0; - hits = 0; - misses = 0; - peakUsedBlocks = 0; - allocatedBitmap = 0; - } + size_t totalAllocations = 0; // 累計確保回数 + size_t totalDeallocations = 0; // 累計解放回数 + size_t hits = 0; // 確保成功回数 + size_t misses = 0; // 確保失敗回数 + size_t peakUsedBlocks = 0; // 最大同時使用ブロック数 + uint32_t allocatedBitmap = 0; // 現在の使用状況(デバッグ用) + + void reset() + { + totalAllocations = 0; + totalDeallocations = 0; + hits = 0; + misses = 0; + peakUsedBlocks = 0; + allocatedBitmap = 0; + } }; #endif @@ -52,73 +53,96 @@ struct PoolStats { class PoolAllocator { public: - PoolAllocator() = default; - ~PoolAllocator(); - - // コピー禁止 - PoolAllocator(const PoolAllocator &) = delete; - PoolAllocator &operator=(const PoolAllocator &) = delete; - - /// @brief プールの初期化 - /// @param memory プール用メモリ領域(外部で確保済み) - /// @param blockSize 各ブロックのサイズ - /// @param blockCount ブロック数(最大32) - /// @param isPSRAM プールがPSRAMかどうか - /// @return 初期化成功ならtrue - bool initialize(void *memory, size_t blockSize, size_t blockCount, - bool isPSRAM = false); - - /// @brief メモリ確保(プールから) - /// @param size 確保サイズ - /// @return 確保したメモリへのポインタ(失敗時はnullptr) - void *allocate(size_t size); - - /// @brief メモリ解放(プールへ) - /// @param ptr 解放するメモリのポインタ - /// @return プール内のポインタならtrue - bool deallocate(void *ptr); - - /// @brief プールがPSRAMかどうか - bool isPSRAM() const { return isPSRAM_; } + PoolAllocator() = default; + ~PoolAllocator(); + + // コピー禁止 + PoolAllocator(const PoolAllocator &) = delete; + PoolAllocator &operator=(const PoolAllocator &) = delete; + + /// @brief プールの初期化 + /// @param memory プール用メモリ領域(外部で確保済み) + /// @param blockSize 各ブロックのサイズ + /// @param blockCount ブロック数(最大32) + /// @param isPSRAM プールがPSRAMかどうか + /// @return 初期化成功ならtrue + bool initialize(void *memory, size_t blockSize, size_t blockCount, bool isPSRAM = false); + + /// @brief メモリ確保(プールから) + /// @param size 確保サイズ + /// @return 確保したメモリへのポインタ(失敗時はnullptr) + void *allocate(size_t size); + + /// @brief メモリ解放(プールへ) + /// @param ptr 解放するメモリのポインタ + /// @return プール内のポインタならtrue + bool deallocate(void *ptr); + + /// @brief プールがPSRAMかどうか + bool isPSRAM() const + { + return isPSRAM_; + } - /// @brief 初期化済みかどうか - bool isInitialized() const { return initialized_; } + /// @brief 初期化済みかどうか + bool isInitialized() const + { + return initialized_; + } - /// @brief ブロックサイズ取得 - size_t blockSize() const { return blockSize_; } + /// @brief ブロックサイズ取得 + size_t blockSize() const + { + return blockSize_; + } - /// @brief ブロック数取得 - size_t blockCount() const { return blockCount_; } + /// @brief ブロック数取得 + size_t blockCount() const + { + return blockCount_; + } - /// @brief 使用中ブロック数取得 - size_t usedBlockCount() const; + /// @brief 使用中ブロック数取得 + size_t usedBlockCount() const; - /// @brief 空きブロック数取得 - size_t freeBlockCount() const { return blockCount_ - usedBlockCount(); } + /// @brief 空きブロック数取得 + size_t freeBlockCount() const + { + return blockCount_ - usedBlockCount(); + } #ifdef FLEXIMG_DEBUG_PERF_METRICS - /// @brief 統計情報取得(デバッグビルド時のみ) - const PoolStats &stats() const { return stats_; } + /// @brief 統計情報取得(デバッグビルド時のみ) + const PoolStats &stats() const + { + return stats_; + } - /// @brief 統計情報リセット(デバッグビルド時のみ) - void resetStats() { stats_.reset(); } + /// @brief 統計情報リセット(デバッグビルド時のみ) + void resetStats() + { + stats_.reset(); + } - /// @brief ピーク使用ブロック数のみリセット(デバッグビルド時のみ) - void resetPeakStats() { stats_.peakUsedBlocks = 0; } + /// @brief ピーク使用ブロック数のみリセット(デバッグビルド時のみ) + void resetPeakStats() + { + stats_.peakUsedBlocks = 0; + } #endif private: - void *poolMemory_ = nullptr; // プール用メモリ領域(外部管理) - size_t blockSize_ = 0; // ブロックサイズ - size_t blockCount_ = 0; // ブロック数 - bool isPSRAM_ = false; // PSRAMかどうか - uint32_t allocatedBitmap_ = 0; // ブロック使用状況 - uint8_t blockCounts_[32] = {}; // 各ブロックの確保ブロック数(連続確保対応) - bool searchFromHead_ = true; // 探索方向(交互に切り替え) + void *poolMemory_ = nullptr; // プール用メモリ領域(外部管理) + size_t blockSize_ = 0; // ブロックサイズ + size_t blockCount_ = 0; // ブロック数 + bool isPSRAM_ = false; // PSRAMかどうか + uint32_t allocatedBitmap_ = 0; // ブロック使用状況 + uint8_t blockCounts_[32] = {}; // 各ブロックの確保ブロック数(連続確保対応) + bool searchFromHead_ = true; // 探索方向(交互に切り替え) #ifdef FLEXIMG_DEBUG_PERF_METRICS - PoolStats stats_; + PoolStats stats_; #endif - bool initialized_ = false; + bool initialized_ = false; }; // ======================================================================== @@ -139,87 +163,101 @@ class PoolAllocator { class PoolAllocatorAdapter : public IAllocator { public: #ifdef FLEXIMG_DEBUG_PERF_METRICS - /// @brief 統計情報(デバッグビルド時のみ有効) - struct Stats { - size_t poolHits = 0; ///< プールから確保成功 - size_t poolMisses = 0; ///< プールから確保失敗(フォールバック) - size_t poolDeallocs = 0; ///< プールへ解放 - size_t defaultDeallocs = 0; ///< DefaultAllocatorへ解放 - size_t lastAllocSize = 0; ///< 最後の確保サイズ - - void reset() { - poolHits = poolMisses = poolDeallocs = defaultDeallocs = 0; - lastAllocSize = 0; - } - }; + /// @brief 統計情報(デバッグビルド時のみ有効) + struct Stats { + size_t poolHits = 0; ///< プールから確保成功 + size_t poolMisses = 0; ///< プールから確保失敗(フォールバック) + size_t poolDeallocs = 0; ///< プールへ解放 + size_t defaultDeallocs = 0; ///< DefaultAllocatorへ解放 + size_t lastAllocSize = 0; ///< 最後の確保サイズ + + void reset() + { + poolHits = poolMisses = poolDeallocs = defaultDeallocs = 0; + lastAllocSize = 0; + } + }; #endif - /// @brief コンストラクタ - /// @param pool 使用するPoolAllocator - /// @param allowFallback - /// プール確保失敗時にDefaultAllocatorへフォールバックするか - explicit PoolAllocatorAdapter(PoolAllocator &pool, bool allowFallback = true) - : pool_(pool), allowFallback_(allowFallback) {} + /// @brief コンストラクタ + /// @param pool 使用するPoolAllocator + /// @param allowFallback + /// プール確保失敗時にDefaultAllocatorへフォールバックするか + explicit PoolAllocatorAdapter(PoolAllocator &pool, bool allowFallback = true) + : pool_(pool), allowFallback_(allowFallback) + { + } - void *allocate(size_t bytes, size_t /* alignment */ = 16) override { + void *allocate(size_t bytes, size_t /* alignment */ = 16) override + { #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.lastAllocSize = bytes; + stats_.lastAllocSize = bytes; #endif - void *ptr = pool_.allocate(bytes); - if (ptr) { + void *ptr = pool_.allocate(bytes); + if (ptr) { #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.poolHits++; + stats_.poolHits++; #endif - return ptr; - } + return ptr; + } - // プールから確保できない場合 + // プールから確保できない場合 #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.poolMisses++; + stats_.poolMisses++; #endif - if (allowFallback_) { - return DefaultAllocator::instance().allocate(bytes); + if (allowFallback_) { + return DefaultAllocator::instance().allocate(bytes); + } + return nullptr; } - return nullptr; - } - void deallocate(void *ptr) override { - if (pool_.deallocate(ptr)) { + void deallocate(void *ptr) override + { + if (pool_.deallocate(ptr)) { #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.poolDeallocs++; + stats_.poolDeallocs++; #endif - } else { - // プール外のポインタはDefaultAllocatorで解放 + } else { + // プール外のポインタはDefaultAllocatorで解放 #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.defaultDeallocs++; + stats_.defaultDeallocs++; #endif - if (allowFallback_) { - DefaultAllocator::instance().deallocate(ptr); - } + if (allowFallback_) { + DefaultAllocator::instance().deallocate(ptr); + } + } } - } - const char *name() const override { return "PoolAllocatorAdapter"; } + const char *name() const override + { + return "PoolAllocatorAdapter"; + } #ifdef FLEXIMG_DEBUG_PERF_METRICS - /// @brief 統計情報取得(デバッグビルド時のみ) - const Stats &stats() const { return stats_; } + /// @brief 統計情報取得(デバッグビルド時のみ) + const Stats &stats() const + { + return stats_; + } - /// @brief 統計情報リセット(デバッグビルド時のみ) - void resetStats() { stats_.reset(); } + /// @brief 統計情報リセット(デバッグビルド時のみ) + void resetStats() + { + stats_.reset(); + } #endif private: - PoolAllocator &pool_; - bool allowFallback_; + PoolAllocator &pool_; + bool allowFallback_; #ifdef FLEXIMG_DEBUG_PERF_METRICS - Stats stats_; + Stats stats_; #endif }; -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -230,156 +268,158 @@ namespace FLEXIMG_NAMESPACE { namespace core { namespace memory { -PoolAllocator::~PoolAllocator() { - // poolMemory_ は外部管理なので解放しない +PoolAllocator::~PoolAllocator() +{ + // poolMemory_ は外部管理なので解放しない } -bool PoolAllocator::initialize(void *memory, size_t blockSize, - size_t blockCount, bool isPSRAM) { - if (initialized_ || !memory || blockSize == 0 || blockCount == 0) { - return false; - } - - if (blockCount > 32) { - return false; // uint32_t制限 - } - - poolMemory_ = memory; - blockSize_ = blockSize; - blockCount_ = blockCount; - isPSRAM_ = isPSRAM; - allocatedBitmap_ = 0; - for (size_t i = 0; i < 32; ++i) { - blockCounts_[i] = 0; - } - - initialized_ = true; - return true; +bool PoolAllocator::initialize(void *memory, size_t blockSize, size_t blockCount, bool isPSRAM) +{ + if (initialized_ || !memory || blockSize == 0 || blockCount == 0) { + return false; + } + + if (blockCount > 32) { + return false; // uint32_t制限 + } + + poolMemory_ = memory; + blockSize_ = blockSize; + blockCount_ = blockCount; + isPSRAM_ = isPSRAM; + allocatedBitmap_ = 0; + for (size_t i = 0; i < 32; ++i) { + blockCounts_[i] = 0; + } + + initialized_ = true; + return true; } -void *PoolAllocator::allocate(size_t size) { - if (!initialized_ || size == 0) { - return nullptr; - } +void *PoolAllocator::allocate(size_t size) +{ + if (!initialized_ || size == 0) { + return nullptr; + } #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.totalAllocations++; + stats_.totalAllocations++; #endif - // 必要なブロック数を計算 - size_t blocksNeeded = (size + blockSize_ - 1) / blockSize_; + // 必要なブロック数を計算 + size_t blocksNeeded = (size + blockSize_ - 1) / blockSize_; - if (blocksNeeded > blockCount_) { + if (blocksNeeded > blockCount_) { #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.misses++; + stats_.misses++; #endif - return nullptr; - } + return nullptr; + } - // 必要なビットパターンを作成 - uint32_t needBitmap = (1U << blocksNeeded) - 1; + // 必要なビットパターンを作成 + uint32_t needBitmap = (1U << blocksNeeded) - 1; - // 探索方向を決定(交互に切り替えてフラグメンテーション軽減) - size_t start = searchFromHead_ ? 0 : blockCount_ - blocksNeeded; - size_t end = blockCount_ - blocksNeeded + 1; - bool forward = searchFromHead_; + // 探索方向を決定(交互に切り替えてフラグメンテーション軽減) + size_t start = searchFromHead_ ? 0 : blockCount_ - blocksNeeded; + size_t end = blockCount_ - blocksNeeded + 1; + bool forward = searchFromHead_; - searchFromHead_ = !searchFromHead_; // 次回は逆方向 + searchFromHead_ = !searchFromHead_; // 次回は逆方向 - // ビットマップで連続空きブロックを探索 - for (size_t idx = 0; idx < end; ++idx) { - size_t i = forward ? idx : (start - idx); - uint32_t shiftedNeed = needBitmap << i; + // ビットマップで連続空きブロックを探索 + for (size_t idx = 0; idx < end; ++idx) { + size_t i = forward ? idx : (start - idx); + uint32_t shiftedNeed = needBitmap << i; - if ((allocatedBitmap_ & shiftedNeed) == 0) { - // 空きブロック発見 - allocatedBitmap_ |= shiftedNeed; - blockCounts_[i] = - static_cast(blocksNeeded); // 確保ブロック数を記録 + if ((allocatedBitmap_ & shiftedNeed) == 0) { + // 空きブロック発見 + allocatedBitmap_ |= shiftedNeed; + blockCounts_[i] = static_cast(blocksNeeded); // 確保ブロック数を記録 #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.hits++; - stats_.allocatedBitmap = allocatedBitmap_; - - // ピーク使用ブロック数を更新 - size_t currentUsed = usedBlockCount(); - if (currentUsed > stats_.peakUsedBlocks) { - stats_.peakUsedBlocks = currentUsed; - } + stats_.hits++; + stats_.allocatedBitmap = allocatedBitmap_; + + // ピーク使用ブロック数を更新 + size_t currentUsed = usedBlockCount(); + if (currentUsed > stats_.peakUsedBlocks) { + stats_.peakUsedBlocks = currentUsed; + } #endif - return static_cast(poolMemory_) + - (static_cast(i) * blockSize_); + return static_cast(poolMemory_) + (static_cast(i) * blockSize_); + } } - } #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.misses++; + stats_.misses++; #endif - return nullptr; + return nullptr; } -bool PoolAllocator::deallocate(void *ptr) { - if (!initialized_ || !ptr) { - return false; - } +bool PoolAllocator::deallocate(void *ptr) +{ + if (!initialized_ || !ptr) { + return false; + } - // プール内のポインタか判定 - uint8_t *poolStart = static_cast(poolMemory_); - size_t poolSize = blockSize_ * blockCount_; - uint8_t *poolEnd = poolStart + poolSize; - uint8_t *p = static_cast(ptr); + // プール内のポインタか判定 + uint8_t *poolStart = static_cast(poolMemory_); + size_t poolSize = blockSize_ * blockCount_; + uint8_t *poolEnd = poolStart + poolSize; + uint8_t *p = static_cast(ptr); - if (p < poolStart || p >= poolEnd) { - return false; // プール外 - } + if (p < poolStart || p >= poolEnd) { + return false; // プール外 + } - // ブロックインデックス計算 - size_t blockIndex = static_cast(p - poolStart) / blockSize_; + // ブロックインデックス計算 + size_t blockIndex = static_cast(p - poolStart) / blockSize_; - if (blockIndex >= blockCount_) { - return false; // 範囲外 - } + if (blockIndex >= blockCount_) { + return false; // 範囲外 + } - // ビットが立っているか確認(確保済みか) - if ((allocatedBitmap_ & (1U << blockIndex)) == 0) { - return false; // 二重解放 - } + // ビットが立っているか確認(確保済みか) + if ((allocatedBitmap_ & (1U << blockIndex)) == 0) { + return false; // 二重解放 + } - // 確保ブロック数を取得 - uint8_t blocksToFree = blockCounts_[blockIndex]; - if (blocksToFree == 0) { - blocksToFree = 1; // フォールバック(通常は起きない) - } + // 確保ブロック数を取得 + uint8_t blocksToFree = blockCounts_[blockIndex]; + if (blocksToFree == 0) { + blocksToFree = 1; // フォールバック(通常は起きない) + } #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.totalDeallocations++; + stats_.totalDeallocations++; #endif - // 確保時のブロック数分のビットをクリア - uint32_t freeBitmap = ((1U << blocksToFree) - 1) << blockIndex; - allocatedBitmap_ &= ~freeBitmap; - blockCounts_[blockIndex] = 0; // 記録をクリア + // 確保時のブロック数分のビットをクリア + uint32_t freeBitmap = ((1U << blocksToFree) - 1) << blockIndex; + allocatedBitmap_ &= ~freeBitmap; + blockCounts_[blockIndex] = 0; // 記録をクリア #ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.allocatedBitmap = allocatedBitmap_; + stats_.allocatedBitmap = allocatedBitmap_; #endif - return true; + return true; } -size_t PoolAllocator::usedBlockCount() const { - size_t count = 0; - uint32_t bitmap = allocatedBitmap_; - while (bitmap) { - count += bitmap & 1; - bitmap >>= 1; - } - return count; +size_t PoolAllocator::usedBlockCount() const +{ + size_t count = 0; + uint32_t bitmap = allocatedBitmap_; + while (bitmap) { + count += bitmap & 1; + bitmap >>= 1; + } + return count; } -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_CORE_MEMORY_POOL_ALLOCATOR_H +#endif // FLEXIMG_CORE_MEMORY_POOL_ALLOCATOR_H diff --git a/src/fleximg/core/node.h b/src/fleximg/core/node.h index 5fddf34..d003697 100644 --- a/src/fleximg/core/node.h +++ b/src/fleximg/core/node.h @@ -36,526 +36,562 @@ namespace core { class Node { public: - virtual ~Node() = default; - - // ======================================== - // コピー/ムーブ操作 - // ======================================== - // - // ノードのコピー/ムーブ時は既存の接続が切断されます。 - // 代入後は新しいノードとして再接続が必要です。 - // - - // デフォルトコンストラクタ - Node() = default; - - // コピーコンストラクタ: ポート構造のみコピー、接続は引き継がない - Node(const Node &other) : context_(nullptr) { - prepareResponse_.status = PrepareStatus::Idle; - initPorts(static_cast(other.inputs_.size()), - static_cast(other.outputs_.size())); - } - - // ムーブコンストラクタ: ポート構造をムーブし、ownerを修正 - Node(Node &&other) noexcept - : inputs_(std::move(other.inputs_)), outputs_(std::move(other.outputs_)), - context_(nullptr) { - prepareResponse_.status = PrepareStatus::Idle; - // ownerポインタを自分に修正 - for (auto &port : inputs_) { - port.owner = this; - } - for (auto &port : outputs_) { - port.owner = this; - } - } - - // コピー代入演算子: 既存接続を切断し、ポート構造のみコピー - Node &operator=(const Node &other) { - if (this != &other) { - disconnectAll(); - initPorts(static_cast(other.inputs_.size()), - static_cast(other.outputs_.size())); - prepareResponse_.status = PrepareStatus::Idle; - context_ = nullptr; - } - return *this; - } - - // ムーブ代入演算子: 既存接続を切断し、ポート構造をムーブ後にowner修正 - Node &operator=(Node &&other) noexcept { - if (this != &other) { - disconnectAll(); - inputs_ = std::move(other.inputs_); - outputs_ = std::move(other.outputs_); - // ownerポインタを自分に修正 - for (auto &port : inputs_) { - port.owner = this; - } - for (auto &port : outputs_) { - port.owner = this; - } - prepareResponse_.status = PrepareStatus::Idle; - context_ = nullptr; - } - return *this; - } - - // ======================================== - // ポートアクセス(詳細API) - // ======================================== - - Port *inputPort(int index = 0) { - return (index >= 0 && index < static_cast(inputs_.size())) - ? &inputs_[static_cast(index)] - : nullptr; - } - - Port *outputPort(int index = 0) { - return (index >= 0 && index < static_cast(outputs_.size())) - ? &outputs_[static_cast(index)] - : nullptr; - } - - int inputPortCount() const { return static_cast(inputs_.size()); } - int outputPortCount() const { return static_cast(outputs_.size()); } - - // ======================================== - // 接続API(簡易API) - // ======================================== - - // このノードの出力をtargetの入力に接続 - bool connectTo(Node &target, int targetInputIndex = 0, int outputIndex = 0) { - Port *out = outputPort(outputIndex); - Port *in = target.inputPort(targetInputIndex); - return (out && in) ? out->connect(*in) : false; - } - - // sourceの出力をこのノードの入力に接続 - bool connectFrom(Node &source, int sourceOutputIndex = 0, - int inputIndex = 0) { - return source.connectTo(*this, inputIndex, sourceOutputIndex); - } - - // ======================================== - // 接続解除 - // ======================================== - - // 全ての入力/出力ポートの接続を解除 - void disconnectAll() { - for (auto &port : inputs_) { - port.disconnect(); - } - for (auto &port : outputs_) { - port.disconnect(); - } - } - - // ======================================== - // 演算子(チェーン接続用) - // ======================================== - - // src >> affine >> sink のような記述を可能にする - Node &operator>>(Node &downstream) { - connectTo(downstream); - return downstream; - } - - Node &operator<<(Node &upstream) { - connectFrom(upstream); - return *this; - } - - // ======================================== - // 新API: 共通処理(派生クラスで実装) - // ======================================== - - // 入力画像から出力画像を生成 - // 入力を改変して返すか、新しいResponseを返す - virtual RenderResponse &process(RenderResponse &input, - const RenderRequest &request) { - (void)request; - return input; // デフォルトはパススルー - } - - // 準備処理(スクリーン情報を受け取る) - virtual void prepare(const RenderRequest &screenInfo) { (void)screenInfo; } - - // 終了処理 - virtual void finalize() { - // デフォルトは何もしない - } - - // ======================================== - // プル型インターフェース(上流側)- Template Method - // ======================================== - - // 上流から画像を取得して処理(finalメソッド) - // 派生クラスはonPullProcess()をオーバーライド - // 戻り値: RenderContext所有のResponse参照(借用) - virtual RenderResponse &pullProcess(const RenderRequest &request) final { - // 共通処理: スキャンライン処理チェック - FLEXIMG_ASSERT(request.height == 1, - "Scanline processing requires height == 1"); - // 共通処理: 準備完了状態チェック - if (prepareResponse_.status != PrepareStatus::Prepared) { - return makeEmptyResponse(request.origin); - } - // 派生クラスのカスタム処理を呼び出し - return onPullProcess(request); - } - - // 上流へ準備を伝播(finalメソッド) - // 派生クラスはonPullPrepare()をオーバーライド - // 戻り値: PrepareResponse(status == Prepared で成功) - virtual PrepareResponse pullPrepare(const PrepareRequest &request) final { - // 共通処理: 状態チェック - bool shouldContinue; - if (!checkPrepareStatus(shouldContinue)) { - PrepareResponse errorResult; - errorResult.status = PrepareStatus::CycleError; - return errorResult; - } - if (!shouldContinue) { - return prepareResponse_; // DAG共有ノード: キャッシュを返す - } - // 共通処理: コンテキストを保持 - context_ = request.context; - - // 派生クラスのカスタム処理を呼び出し - PrepareResponse result = onPullPrepare(request); - - // 共通処理: 状態更新・結果キャッシュ - prepareResponse_ = result; - return result; - } - - // 上流へ終了を伝播(finalメソッド) - // 派生クラスはonPullFinalize()をオーバーライド - virtual void pullFinalize() final { - // 共通処理: 循環防止 - if (prepareResponse_.status == PrepareStatus::Idle) { - return; - } - // 共通処理: 状態リセット - prepareResponse_.status = PrepareStatus::Idle; - - // 派生クラスのカスタム処理を呼び出し - // 注: context_はonPullFinalize()で使用される可能性があるため、 - // クリアはonPullFinalize()の後に行う - onPullFinalize(); - - // 共通処理: コンテキストをクリア - context_ = nullptr; - } - - // ======================================== - // プッシュ型インターフェース(下流側)- Template Method - // ======================================== - - // 上流から画像を受け取って処理し、下流へ渡す(finalメソッド) - // 派生クラスはonPushProcess()をオーバーライド - virtual void pushProcess(RenderResponse &input, - const RenderRequest &request) final { - // 共通処理: スキャンライン処理チェック - FLEXIMG_ASSERT(request.height == 1, - "Scanline processing requires height == 1"); - // 共通処理: 準備完了状態チェック - if (prepareResponse_.status != PrepareStatus::Prepared) { - return; - } - // 派生クラスのカスタム処理を呼び出し - onPushProcess(input, request); - } - - // 下流へ準備を伝播(finalメソッド) - // 派生クラスはonPushPrepare()をオーバーライド - // 戻り値: PrepareResponse(status == Prepared で成功) - virtual PrepareResponse pushPrepare(const PrepareRequest &request) final { - // 共通処理: 状態チェック - bool shouldContinue; - if (!checkPrepareStatus(shouldContinue)) { - PrepareResponse errorResult; - errorResult.status = PrepareStatus::CycleError; - return errorResult; - } - if (!shouldContinue) { - return prepareResponse_; // DAG共有ノード: キャッシュを返す - } - // 共通処理: コンテキストを保持 - context_ = request.context; - - // 派生クラスのカスタム処理を呼び出し - PrepareResponse result = onPushPrepare(request); - - // 共通処理: 状態更新・結果キャッシュ - prepareResponse_ = result; - return result; - } - - // 下流へ終了を伝播(finalメソッド) - // 派生クラスはonPushFinalize()をオーバーライド - virtual void pushFinalize() final { - // 共通処理: 循環防止 - if (prepareResponse_.status == PrepareStatus::Idle) { - return; - } - // 共通処理: 状態リセット - prepareResponse_.status = PrepareStatus::Idle; - - // 派生クラスのカスタム処理を呼び出し - // 注: context_はonPushFinalize()で使用される可能性があるため、 - // クリアはonPushFinalize()の後に行う - onPushFinalize(); - - // 共通処理: コンテキストをクリア - context_ = nullptr; - } - - // ノード名(デバッグ用) - virtual const char *name() const { return "Node"; } - - // ======================================== - // メトリクス用ノードタイプ - // ======================================== - - // 派生クラスでオーバーライドしてNodeType::Xxxを返す - virtual int nodeTypeForMetrics() const { return 0; } - - // ======================================== - // 範囲判定(最適化用) - // ======================================== - - // このノードがrequestに対して提供できるデータ範囲を取得(スキャンライン単位) - // スキャンラインごとの正確な有効ピクセル範囲を返す - // デフォルト: 上流があればパススルー、なければ空(データなし) - // 派生クラス: 範囲を変更するノード(CompositeNode等)はオーバーライド - virtual DataRange getDataRange(const RenderRequest &request) const { - Node *upstream = upstreamNode(0); - if (upstream) { - return upstream->getDataRange(request); // 上流パススルー - } - return DataRange{0, 0}; // 上流なしはデータなし - } - - // このノードの出力データ範囲の上限(AABB由来)を取得 - // 全スキャンラインに共通する最大範囲を返す(バッファサイズ見積もり用) - // Prepare段階で計算済みのAABBを使用するため、計算コストはほぼゼロ - DataRange getDataRangeBounds(const RenderRequest &request) const { - return prepareResponse_.getDataRange(request); - } - - // prepare応答を取得(派生クラスでの判定用) - const PrepareResponse &lastPrepareResponse() const { - return prepareResponse_; - } - - // ======================================== - // ノードアクセス - // ======================================== - - // 上流ノードを取得(入力ポート経由) - Node *upstreamNode(int inputIndex = 0) const { - if (inputIndex < 0 || inputIndex >= static_cast(inputs_.size())) { - return nullptr; - } - return inputs_[static_cast(inputIndex)].connectedNode(); - } - - // 下流ノードを取得(出力ポート経由) - Node *downstreamNode(int outputIndex = 0) const { - if (outputIndex < 0 || outputIndex >= static_cast(outputs_.size())) { - return nullptr; - } - return outputs_[static_cast(outputIndex)].connectedNode(); - } - - // ======================================== - // コンテキストアクセス - // ======================================== - - // prepare時に設定されたコンテキストを取得 - // 設定されていない場合はnullptrを返す - RenderContext *context() const { return context_; } - - // prepare時に設定されたアロケータを取得(context経由) - // 設定されていない場合はnullptrを返す - core::memory::IAllocator *allocator() const { - return context_ ? context_->allocator() : nullptr; - } - - // prepare時に設定されたエントリプールを取得(context経由) - // 設定されていない場合はnullptrを返す - ImageBufferEntryPool *entryPool() const { - return context_ ? context_->entryPool() : nullptr; - } - - // ======================================== - // バッファ整理ヘルパー - // ======================================== - - // RenderResponseのバッファを整理(validSegments処理 + フォーマット変換) - // format: 変換先フォーマット(デフォルト: RGBA8_Straight) - void - consolidateIfNeeded(RenderResponse &input, - PixelFormatID format = PixelFormatIDs::RGBA8_Straight); - - // ======================================== - // RenderResponse取得ヘルパー - // ======================================== - - /// @brief RenderResponseを取得しバッファを設定 - /// @param buf 画像バッファ - /// @param origin 原点(ワールド座標) - /// @return RenderContext所有のResponse参照 - RenderResponse &makeResponse(ImageBuffer &&buf, Point origin); - - /// @brief 空のRenderResponseを取得 - /// @param origin 原点(ワールド座標) - /// @return RenderContext所有の空Response参照 - RenderResponse &makeEmptyResponse(Point origin); + virtual ~Node() = default; + + // ======================================== + // コピー/ムーブ操作 + // ======================================== + // + // ノードのコピー/ムーブ時は既存の接続が切断されます。 + // 代入後は新しいノードとして再接続が必要です。 + // + + // デフォルトコンストラクタ + Node() = default; + + // コピーコンストラクタ: ポート構造のみコピー、接続は引き継がない + Node(const Node &other) : context_(nullptr) + { + prepareResponse_.status = PrepareStatus::Idle; + initPorts(static_cast(other.inputs_.size()), static_cast(other.outputs_.size())); + } + + // ムーブコンストラクタ: ポート構造をムーブし、ownerを修正 + Node(Node &&other) noexcept + : inputs_(std::move(other.inputs_)), outputs_(std::move(other.outputs_)), context_(nullptr) + { + prepareResponse_.status = PrepareStatus::Idle; + // ownerポインタを自分に修正 + for (auto &port : inputs_) { + port.owner = this; + } + for (auto &port : outputs_) { + port.owner = this; + } + } + + // コピー代入演算子: 既存接続を切断し、ポート構造のみコピー + Node &operator=(const Node &other) + { + if (this != &other) { + disconnectAll(); + initPorts(static_cast(other.inputs_.size()), + static_cast(other.outputs_.size())); + prepareResponse_.status = PrepareStatus::Idle; + context_ = nullptr; + } + return *this; + } + + // ムーブ代入演算子: 既存接続を切断し、ポート構造をムーブ後にowner修正 + Node &operator=(Node &&other) noexcept + { + if (this != &other) { + disconnectAll(); + inputs_ = std::move(other.inputs_); + outputs_ = std::move(other.outputs_); + // ownerポインタを自分に修正 + for (auto &port : inputs_) { + port.owner = this; + } + for (auto &port : outputs_) { + port.owner = this; + } + prepareResponse_.status = PrepareStatus::Idle; + context_ = nullptr; + } + return *this; + } + + // ======================================== + // ポートアクセス(詳細API) + // ======================================== + + Port *inputPort(int index = 0) + { + return (index >= 0 && index < static_cast(inputs_.size())) ? &inputs_[static_cast(index)] + : nullptr; + } + + Port *outputPort(int index = 0) + { + return (index >= 0 && index < static_cast(outputs_.size())) ? &outputs_[static_cast(index)] + : nullptr; + } + + int inputPortCount() const + { + return static_cast(inputs_.size()); + } + int outputPortCount() const + { + return static_cast(outputs_.size()); + } + + // ======================================== + // 接続API(簡易API) + // ======================================== + + // このノードの出力をtargetの入力に接続 + bool connectTo(Node &target, int targetInputIndex = 0, int outputIndex = 0) + { + Port *out = outputPort(outputIndex); + Port *in = target.inputPort(targetInputIndex); + return (out && in) ? out->connect(*in) : false; + } + + // sourceの出力をこのノードの入力に接続 + bool connectFrom(Node &source, int sourceOutputIndex = 0, int inputIndex = 0) + { + return source.connectTo(*this, inputIndex, sourceOutputIndex); + } + + // ======================================== + // 接続解除 + // ======================================== + + // 全ての入力/出力ポートの接続を解除 + void disconnectAll() + { + for (auto &port : inputs_) { + port.disconnect(); + } + for (auto &port : outputs_) { + port.disconnect(); + } + } + + // ======================================== + // 演算子(チェーン接続用) + // ======================================== + + // src >> affine >> sink のような記述を可能にする + Node &operator>>(Node &downstream) + { + connectTo(downstream); + return downstream; + } + + Node &operator<<(Node &upstream) + { + connectFrom(upstream); + return *this; + } + + // ======================================== + // 新API: 共通処理(派生クラスで実装) + // ======================================== + + // 入力画像から出力画像を生成 + // 入力を改変して返すか、新しいResponseを返す + virtual RenderResponse &process(RenderResponse &input, const RenderRequest &request) + { + (void)request; + return input; // デフォルトはパススルー + } + + // 準備処理(スクリーン情報を受け取る) + virtual void prepare(const RenderRequest &screenInfo) + { + (void)screenInfo; + } + + // 終了処理 + virtual void finalize() + { + // デフォルトは何もしない + } + + // ======================================== + // プル型インターフェース(上流側)- Template Method + // ======================================== + + // 上流から画像を取得して処理(finalメソッド) + // 派生クラスはonPullProcess()をオーバーライド + // 戻り値: RenderContext所有のResponse参照(借用) + virtual RenderResponse &pullProcess(const RenderRequest &request) final + { + // 共通処理: スキャンライン処理チェック + FLEXIMG_ASSERT(request.height == 1, "Scanline processing requires height == 1"); + // 共通処理: 準備完了状態チェック + if (prepareResponse_.status != PrepareStatus::Prepared) { + return makeEmptyResponse(request.origin); + } + // 派生クラスのカスタム処理を呼び出し + return onPullProcess(request); + } + + // 上流へ準備を伝播(finalメソッド) + // 派生クラスはonPullPrepare()をオーバーライド + // 戻り値: PrepareResponse(status == Prepared で成功) + virtual PrepareResponse pullPrepare(const PrepareRequest &request) final + { + // 共通処理: 状態チェック + bool shouldContinue; + if (!checkPrepareStatus(shouldContinue)) { + PrepareResponse errorResult; + errorResult.status = PrepareStatus::CycleError; + return errorResult; + } + if (!shouldContinue) { + return prepareResponse_; // DAG共有ノード: キャッシュを返す + } + // 共通処理: コンテキストを保持 + context_ = request.context; + + // 派生クラスのカスタム処理を呼び出し + PrepareResponse result = onPullPrepare(request); + + // 共通処理: 状態更新・結果キャッシュ + prepareResponse_ = result; + return result; + } + + // 上流へ終了を伝播(finalメソッド) + // 派生クラスはonPullFinalize()をオーバーライド + virtual void pullFinalize() final + { + // 共通処理: 循環防止 + if (prepareResponse_.status == PrepareStatus::Idle) { + return; + } + // 共通処理: 状態リセット + prepareResponse_.status = PrepareStatus::Idle; + + // 派生クラスのカスタム処理を呼び出し + // 注: context_はonPullFinalize()で使用される可能性があるため、 + // クリアはonPullFinalize()の後に行う + onPullFinalize(); + + // 共通処理: コンテキストをクリア + context_ = nullptr; + } + + // ======================================== + // プッシュ型インターフェース(下流側)- Template Method + // ======================================== + + // 上流から画像を受け取って処理し、下流へ渡す(finalメソッド) + // 派生クラスはonPushProcess()をオーバーライド + virtual void pushProcess(RenderResponse &input, const RenderRequest &request) final + { + // 共通処理: スキャンライン処理チェック + FLEXIMG_ASSERT(request.height == 1, "Scanline processing requires height == 1"); + // 共通処理: 準備完了状態チェック + if (prepareResponse_.status != PrepareStatus::Prepared) { + return; + } + // 派生クラスのカスタム処理を呼び出し + onPushProcess(input, request); + } + + // 下流へ準備を伝播(finalメソッド) + // 派生クラスはonPushPrepare()をオーバーライド + // 戻り値: PrepareResponse(status == Prepared で成功) + virtual PrepareResponse pushPrepare(const PrepareRequest &request) final + { + // 共通処理: 状態チェック + bool shouldContinue; + if (!checkPrepareStatus(shouldContinue)) { + PrepareResponse errorResult; + errorResult.status = PrepareStatus::CycleError; + return errorResult; + } + if (!shouldContinue) { + return prepareResponse_; // DAG共有ノード: キャッシュを返す + } + // 共通処理: コンテキストを保持 + context_ = request.context; + + // 派生クラスのカスタム処理を呼び出し + PrepareResponse result = onPushPrepare(request); + + // 共通処理: 状態更新・結果キャッシュ + prepareResponse_ = result; + return result; + } + + // 下流へ終了を伝播(finalメソッド) + // 派生クラスはonPushFinalize()をオーバーライド + virtual void pushFinalize() final + { + // 共通処理: 循環防止 + if (prepareResponse_.status == PrepareStatus::Idle) { + return; + } + // 共通処理: 状態リセット + prepareResponse_.status = PrepareStatus::Idle; + + // 派生クラスのカスタム処理を呼び出し + // 注: context_はonPushFinalize()で使用される可能性があるため、 + // クリアはonPushFinalize()の後に行う + onPushFinalize(); + + // 共通処理: コンテキストをクリア + context_ = nullptr; + } + + // ノード名(デバッグ用) + virtual const char *name() const + { + return "Node"; + } + + // ======================================== + // メトリクス用ノードタイプ + // ======================================== + + // 派生クラスでオーバーライドしてNodeType::Xxxを返す + virtual int nodeTypeForMetrics() const + { + return 0; + } + + // ======================================== + // 範囲判定(最適化用) + // ======================================== + + // このノードがrequestに対して提供できるデータ範囲を取得(スキャンライン単位) + // スキャンラインごとの正確な有効ピクセル範囲を返す + // デフォルト: 上流があればパススルー、なければ空(データなし) + // 派生クラス: 範囲を変更するノード(CompositeNode等)はオーバーライド + virtual DataRange getDataRange(const RenderRequest &request) const + { + Node *upstream = upstreamNode(0); + if (upstream) { + return upstream->getDataRange(request); // 上流パススルー + } + return DataRange{0, 0}; // 上流なしはデータなし + } + + // このノードの出力データ範囲の上限(AABB由来)を取得 + // 全スキャンラインに共通する最大範囲を返す(バッファサイズ見積もり用) + // Prepare段階で計算済みのAABBを使用するため、計算コストはほぼゼロ + DataRange getDataRangeBounds(const RenderRequest &request) const + { + return prepareResponse_.getDataRange(request); + } + + // prepare応答を取得(派生クラスでの判定用) + const PrepareResponse &lastPrepareResponse() const + { + return prepareResponse_; + } + + // ======================================== + // ノードアクセス + // ======================================== + + // 上流ノードを取得(入力ポート経由) + Node *upstreamNode(int inputIndex = 0) const + { + if (inputIndex < 0 || inputIndex >= static_cast(inputs_.size())) { + return nullptr; + } + return inputs_[static_cast(inputIndex)].connectedNode(); + } + + // 下流ノードを取得(出力ポート経由) + Node *downstreamNode(int outputIndex = 0) const + { + if (outputIndex < 0 || outputIndex >= static_cast(outputs_.size())) { + return nullptr; + } + return outputs_[static_cast(outputIndex)].connectedNode(); + } + + // ======================================== + // コンテキストアクセス + // ======================================== + + // prepare時に設定されたコンテキストを取得 + // 設定されていない場合はnullptrを返す + RenderContext *context() const + { + return context_; + } + + // prepare時に設定されたアロケータを取得(context経由) + // 設定されていない場合はnullptrを返す + core::memory::IAllocator *allocator() const + { + return context_ ? context_->allocator() : nullptr; + } + + // prepare時に設定されたエントリプールを取得(context経由) + // 設定されていない場合はnullptrを返す + ImageBufferEntryPool *entryPool() const + { + return context_ ? context_->entryPool() : nullptr; + } + + // ======================================== + // バッファ整理ヘルパー + // ======================================== + + // RenderResponseのバッファを整理(validSegments処理 + フォーマット変換) + // format: 変換先フォーマット(デフォルト: RGBA8_Straight) + void consolidateIfNeeded(RenderResponse &input, PixelFormatID format = PixelFormatIDs::RGBA8_Straight); + + // ======================================== + // RenderResponse取得ヘルパー + // ======================================== + + /// @brief RenderResponseを取得しバッファを設定 + /// @param buf 画像バッファ + /// @param origin 原点(ワールド座標) + /// @return RenderContext所有のResponse参照 + RenderResponse &makeResponse(ImageBuffer &&buf, Point origin); + + /// @brief 空のRenderResponseを取得 + /// @param origin 原点(ワールド座標) + /// @return RenderContext所有の空Response参照 + RenderResponse &makeEmptyResponse(Point origin); protected: - std::vector inputs_; - std::vector outputs_; - - // 準備応答キャッシュ(状態 + AABB情報、DAG共有ノード用) - // status フィールドで循環参照検出にも使用 - PrepareResponse prepareResponse_; - - // RendererNodeから伝播されるコンテキスト(prepare時に保持、finalize時にクリア) - // allocator, entryPool 等のパイプラインリソースを統合管理 - RenderContext *context_ = nullptr; - - // ======================================== - // Template Method フック(派生クラスでオーバーライド) - // ======================================== - - // pullPrepare()から呼ばれるフック - // デフォルト: 上流ノードへ伝播し、prepare()を呼び出す - virtual PrepareResponse onPullPrepare(const PrepareRequest &request) { - // 上流へ伝播 - Node *upstream = upstreamNode(0); - if (upstream) { - PrepareResponse result = upstream->pullPrepare(request); - if (!result.ok()) { + std::vector inputs_; + std::vector outputs_; + + // 準備応答キャッシュ(状態 + AABB情報、DAG共有ノード用) + // status フィールドで循環参照検出にも使用 + PrepareResponse prepareResponse_; + + // RendererNodeから伝播されるコンテキスト(prepare時に保持、finalize時にクリア) + // allocator, entryPool 等のパイプラインリソースを統合管理 + RenderContext *context_ = nullptr; + + // ======================================== + // Template Method フック(派生クラスでオーバーライド) + // ======================================== + + // pullPrepare()から呼ばれるフック + // デフォルト: 上流ノードへ伝播し、prepare()を呼び出す + virtual PrepareResponse onPullPrepare(const PrepareRequest &request) + { + // 上流へ伝播 + Node *upstream = upstreamNode(0); + if (upstream) { + PrepareResponse result = upstream->pullPrepare(request); + if (!result.ok()) { + return result; + } + // 準備処理(PrepareRequestからRenderRequest相当の情報を渡す) + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + return result; // 上流の結果をパススルー + } + // 上流なし: 自身の情報を返す(末端ノード) + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + PrepareResponse result; + result.status = PrepareStatus::Prepared; + result.width = request.width; + result.height = request.height; + result.origin = request.origin; return result; - } - // 準備処理(PrepareRequestからRenderRequest相当の情報を渡す) - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - return result; // 上流の結果をパススルー - } - // 上流なし: 自身の情報を返す(末端ノード) - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - PrepareResponse result; - result.status = PrepareStatus::Prepared; - result.width = request.width; - result.height = request.height; - result.origin = request.origin; - return result; - } - - // pushPrepare()から呼ばれるフック - // デフォルト: prepare()を呼び出し、下流ノードへ伝播 - virtual PrepareResponse onPushPrepare(const PrepareRequest &request) { - // 準備処理 - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - // 下流へ伝播 - Node *downstream = downstreamNode(0); - if (downstream) { - PrepareResponse result = downstream->pushPrepare(request); - return result; // 下流の結果をパススルー - } - // 下流なし: 自身の情報を返す(末端ノード) - PrepareResponse result; - result.status = PrepareStatus::Prepared; - result.width = request.width; - result.height = request.height; - result.origin = request.origin; - return result; - } - - // pullProcess()から呼ばれるフック - // デフォルト: 上流からpullしてprocess()を呼び出す - virtual RenderResponse &onPullProcess(const RenderRequest &request) { - Node *upstream = upstreamNode(0); - if (!upstream) - return makeEmptyResponse(request.origin); - RenderResponse &input = upstream->pullProcess(request); - return process(input, request); - } - - // pushProcess()から呼ばれるフック - // デフォルト: process()を呼び出して下流へpush - virtual void onPushProcess(RenderResponse &input, - const RenderRequest &request) { - RenderResponse &output = process(input, request); - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushProcess(output, request); - } - } - - // pullFinalize()から呼ばれるフック - // デフォルト: finalize()を呼び出し、上流へ伝播 - virtual void onPullFinalize() { - finalize(); - Node *upstream = upstreamNode(0); - if (upstream) { - upstream->pullFinalize(); - } - } - - // pushFinalize()から呼ばれるフック - // デフォルト: 下流へ伝播し、finalize()を呼び出す - virtual void onPushFinalize() { - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushFinalize(); - } - finalize(); - } - - // ======================================== - // ヘルパーメソッド - // ======================================== - - // 循環参照チェック(pullPrepare/pushPrepare共通) - // prepareResponse_.status を参照・更新する - bool checkPrepareStatus(bool &shouldContinue); - - // フォーマット変換ヘルパー(メトリクス記録付き) - // converter: - // 事前解決済みのFormatConverterを渡すことで、prepare段階で解決済みの - // コンバータを再利用でき、processループ内の負荷を軽減できる - ImageBuffer - convertFormat(ImageBuffer &&buffer, PixelFormatID target, - FormatConversion mode = FormatConversion::CopyIfNeeded, - const FormatConverter *converter = nullptr); - - // 派生クラス用:ポート初期化 - void initPorts(int_fast16_t inputCount, int_fast16_t outputCount); + } + + // pushPrepare()から呼ばれるフック + // デフォルト: prepare()を呼び出し、下流ノードへ伝播 + virtual PrepareResponse onPushPrepare(const PrepareRequest &request) + { + // 準備処理 + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + // 下流へ伝播 + Node *downstream = downstreamNode(0); + if (downstream) { + PrepareResponse result = downstream->pushPrepare(request); + return result; // 下流の結果をパススルー + } + // 下流なし: 自身の情報を返す(末端ノード) + PrepareResponse result; + result.status = PrepareStatus::Prepared; + result.width = request.width; + result.height = request.height; + result.origin = request.origin; + return result; + } + + // pullProcess()から呼ばれるフック + // デフォルト: 上流からpullしてprocess()を呼び出す + virtual RenderResponse &onPullProcess(const RenderRequest &request) + { + Node *upstream = upstreamNode(0); + if (!upstream) return makeEmptyResponse(request.origin); + RenderResponse &input = upstream->pullProcess(request); + return process(input, request); + } + + // pushProcess()から呼ばれるフック + // デフォルト: process()を呼び出して下流へpush + virtual void onPushProcess(RenderResponse &input, const RenderRequest &request) + { + RenderResponse &output = process(input, request); + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(output, request); + } + } + + // pullFinalize()から呼ばれるフック + // デフォルト: finalize()を呼び出し、上流へ伝播 + virtual void onPullFinalize() + { + finalize(); + Node *upstream = upstreamNode(0); + if (upstream) { + upstream->pullFinalize(); + } + } + + // pushFinalize()から呼ばれるフック + // デフォルト: 下流へ伝播し、finalize()を呼び出す + virtual void onPushFinalize() + { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushFinalize(); + } + finalize(); + } + + // ======================================== + // ヘルパーメソッド + // ======================================== + + // 循環参照チェック(pullPrepare/pushPrepare共通) + // prepareResponse_.status を参照・更新する + bool checkPrepareStatus(bool &shouldContinue); + + // フォーマット変換ヘルパー(メトリクス記録付き) + // converter: + // 事前解決済みのFormatConverterを渡すことで、prepare段階で解決済みの + // コンバータを再利用でき、processループ内の負荷を軽減できる + ImageBuffer convertFormat(ImageBuffer &&buffer, PixelFormatID target, + FormatConversion mode = FormatConversion::CopyIfNeeded, + const FormatConverter *converter = nullptr); + + // 派生クラス用:ポート初期化 + void initPorts(int_fast16_t inputCount, int_fast16_t outputCount); }; -} // namespace core +} // namespace core // [DEPRECATED] 後方互換性のため親名前空間に公開。将来廃止予定。 // 新規コードでは core:: プレフィックスを使用してください。 using core::Node; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -573,112 +609,114 @@ namespace core { // prepareResponse_.status を参照・更新する // 戻り値: true=成功, false=エラー // shouldContinue: true=処理継続, false=スキップ(Prepared)またはエラー -bool Node::checkPrepareStatus(bool &shouldContinue) { - PrepareStatus &status = prepareResponse_.status; - if (status == PrepareStatus::Preparing) { - status = PrepareStatus::CycleError; - shouldContinue = false; - return false; // 循環参照検出 - } - if (status == PrepareStatus::Prepared) { - shouldContinue = false; - return true; // 成功(DAG共有、スキップ) - } - if (status == PrepareStatus::CycleError) { - shouldContinue = false; - return false; // 既にエラー状態 - } - status = PrepareStatus::Preparing; - shouldContinue = true; - return true; // 成功(処理継続) +bool Node::checkPrepareStatus(bool &shouldContinue) +{ + PrepareStatus &status = prepareResponse_.status; + if (status == PrepareStatus::Preparing) { + status = PrepareStatus::CycleError; + shouldContinue = false; + return false; // 循環参照検出 + } + if (status == PrepareStatus::Prepared) { + shouldContinue = false; + return true; // 成功(DAG共有、スキップ) + } + if (status == PrepareStatus::CycleError) { + shouldContinue = false; + return false; // 既にエラー状態 + } + status = PrepareStatus::Preparing; + shouldContinue = true; + return true; // 成功(処理継続) } // フォーマット変換ヘルパー(メトリクス記録付き) // 参照モードから所有モードに変わった場合、ノード別統計に記録 // allocator()を使用してバッファを確保する -ImageBuffer Node::convertFormat(ImageBuffer &&buffer, PixelFormatID target, - FormatConversion mode, - const FormatConverter *converter) { - bool wasOwning = buffer.ownsMemory(); - - // 参照モードの場合、ノードのallocator()を新バッファ用に渡す - // 注: setAllocator()で参照バッファのallocatorを変更すると、 - // デストラクタが非所有メモリを解放しようとするバグがあるため、 - // toFormat()のallocパラメータで安全に渡す - core::memory::IAllocator *newAlloc = wasOwning ? nullptr : allocator(); - - ImageBuffer result = - std::move(buffer).toFormat(target, mode, newAlloc, converter); - - // 参照→所有モードへの変換時にメトリクス記録 - if (!wasOwning && result.ownsMemory()) { +ImageBuffer Node::convertFormat(ImageBuffer &&buffer, PixelFormatID target, FormatConversion mode, + const FormatConverter *converter) +{ + bool wasOwning = buffer.ownsMemory(); + + // 参照モードの場合、ノードのallocator()を新バッファ用に渡す + // 注: setAllocator()で参照バッファのallocatorを変更すると、 + // デストラクタが非所有メモリを解放しようとするバグがあるため、 + // toFormat()のallocパラメータで安全に渡す + core::memory::IAllocator *newAlloc = wasOwning ? nullptr : allocator(); + + ImageBuffer result = std::move(buffer).toFormat(target, mode, newAlloc, converter); + + // 参照→所有モードへの変換時にメトリクス記録 + if (!wasOwning && result.ownsMemory()) { #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().nodes[nodeTypeForMetrics()].recordAlloc( - result.totalBytes(), result.width(), result.height()); + PerfMetrics::instance().nodes[nodeTypeForMetrics()].recordAlloc(result.totalBytes(), result.width(), + result.height()); #endif - } - return result; + } + return result; } // 派生クラス用:ポート初期化 -void Node::initPorts(int_fast16_t inputCount, int_fast16_t outputCount) { - inputs_.resize(static_cast(inputCount)); - outputs_.resize(static_cast(outputCount)); - for (int_fast16_t i = 0; i < inputCount; ++i) { - inputs_[static_cast(i)] = Port(this, i); - } - for (int_fast16_t i = 0; i < outputCount; ++i) { - outputs_[static_cast(i)] = Port(this, i); - } +void Node::initPorts(int_fast16_t inputCount, int_fast16_t outputCount) +{ + inputs_.resize(static_cast(inputCount)); + outputs_.resize(static_cast(outputCount)); + for (int_fast16_t i = 0; i < inputCount; ++i) { + inputs_[static_cast(i)] = Port(this, i); + } + for (int_fast16_t i = 0; i < outputCount; ++i) { + outputs_[static_cast(i)] = Port(this, i); + } } // バッファ整理ヘルパー // フォーマット変換を行う -void Node::consolidateIfNeeded(RenderResponse &input, PixelFormatID format) { - if (input.empty()) { - return; - } - - // フォーマット変換が必要な場合 - // convertFormat()経由でメトリクス記録を維持 - if (format != nullptr) { - PixelFormatID srcFormat = input.buffer().formatID(); - if (srcFormat != format) { - ImageBuffer converted = convertFormat(std::move(input.buffer()), format); - input.replaceBuffer(std::move(converted)); - } - } - - // バッファoriginをresponse.originに同期 - input.origin = input.buffer().origin(); +void Node::consolidateIfNeeded(RenderResponse &input, PixelFormatID format) +{ + if (input.empty()) { + return; + } + + // フォーマット変換が必要な場合 + // convertFormat()経由でメトリクス記録を維持 + if (format != nullptr) { + PixelFormatID srcFormat = input.buffer().formatID(); + if (srcFormat != format) { + ImageBuffer converted = convertFormat(std::move(input.buffer()), format); + input.replaceBuffer(std::move(converted)); + } + } + + // バッファoriginをresponse.originに同期 + input.origin = input.buffer().origin(); } // RenderResponse構築ヘルパー // RenderContext経由でResponseを取得し、バッファにワールド座標originを設定して追加 -RenderResponse &Node::makeResponse(ImageBuffer &&buf, Point origin) { - FLEXIMG_ASSERT(context_ != nullptr, - "RenderContext required for makeResponse"); - RenderResponse &resp = context_->acquireResponse(); - if (buf.isValid()) { - buf.setOrigin(origin); - resp.addBuffer(std::move(buf)); - } - resp.origin = origin; - return resp; +RenderResponse &Node::makeResponse(ImageBuffer &&buf, Point origin) +{ + FLEXIMG_ASSERT(context_ != nullptr, "RenderContext required for makeResponse"); + RenderResponse &resp = context_->acquireResponse(); + if (buf.isValid()) { + buf.setOrigin(origin); + resp.addBuffer(std::move(buf)); + } + resp.origin = origin; + return resp; } // 空のRenderResponseを構築 -RenderResponse &Node::makeEmptyResponse(Point origin) { - FLEXIMG_ASSERT(context_ != nullptr, - "RenderContext required for makeEmptyResponse"); - RenderResponse &resp = context_->acquireResponse(); - resp.origin = origin; - return resp; +RenderResponse &Node::makeEmptyResponse(Point origin) +{ + FLEXIMG_ASSERT(context_ != nullptr, "RenderContext required for makeEmptyResponse"); + RenderResponse &resp = context_->acquireResponse(); + resp.origin = origin; + return resp; } -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_NODE_H +#endif // FLEXIMG_NODE_H diff --git a/src/fleximg/core/perf_metrics.h b/src/fleximg/core/perf_metrics.h index 9adc797..1f4b8dd 100644 --- a/src/fleximg/core/perf_metrics.h +++ b/src/fleximg/core/perf_metrics.h @@ -39,27 +39,27 @@ namespace core { namespace NodeType { // システム系 -constexpr int Renderer = 0; // パイプライン発火点(exec()全体時間を記録) -constexpr int Source = 1; // 画像入力 -constexpr int Sink = 2; // 画像出力 -constexpr int Distributor = 3; // 分配(1入力→N出力) +constexpr int Renderer = 0; // パイプライン発火点(exec()全体時間を記録) +constexpr int Source = 1; // 画像入力 +constexpr int Sink = 2; // 画像出力 +constexpr int Distributor = 3; // 分配(1入力→N出力) // 構造系 -constexpr int Affine = 4; // アフィン変換 -constexpr int Composite = 5; // 合成(N入力→1出力) +constexpr int Affine = 4; // アフィン変換 +constexpr int Composite = 5; // 合成(N入力→1出力) // フィルタ系 constexpr int Brightness = 6; -constexpr int Grayscale = 7; +constexpr int Grayscale = 7; // 8: 廃止(旧BoxBlur) -constexpr int Alpha = 9; +constexpr int Alpha = 9; constexpr int HorizontalBlur = 10; -constexpr int VerticalBlur = 11; +constexpr int VerticalBlur = 11; // 特殊ソース系 -constexpr int NinePatch = 12; // 9patch画像 +constexpr int NinePatch = 12; // 9patch画像 // 合成系 -constexpr int Matte = 13; // マット合成(3入力) +constexpr int Matte = 13; // マット合成(3入力) constexpr int Count = 14; -} // namespace NodeType +} // namespace NodeType // コンパイル時チェック: 最後のノードタイプ + 1 == Count // ノード追加時に Count の更新を忘れるとここでエラーになる @@ -78,127 +78,132 @@ static_assert(NodeType::VerticalBlur == 11, // ノード別メトリクス struct NodeMetrics { - uint32_t time_us = 0; // 処理時間(マイクロ秒) - uint32_t count = 0; // 呼び出し回数 - uint32_t requestedPixels = 0; // 上流に要求したピクセル数 - uint32_t usedPixels = 0; // 実際に使用したピクセル数 - uint32_t theoreticalMinPixels = 0; // 理論最小ピクセル数(分割時の推定値) - uint32_t allocatedBytes = 0; // このノードが確保したバイト数 - uint32_t allocCount = 0; // 確保回数 - uint32_t maxAllocBytes = 0; // 一回の最大確保バイト数 - int16_t maxAllocWidth = 0; // その時の幅 - int16_t maxAllocHeight = 0; // その時の高さ - - void reset() { *this = NodeMetrics{}; } - - // 現在のピクセル効率(0.0〜1.0): usedPixels / requestedPixels - float pixelEfficiency() const { - if (requestedPixels == 0) - return 1.0f; - return static_cast(usedPixels) / static_cast(requestedPixels); - } - - // 不要ピクセル率(0.0〜1.0) - float wasteRatio() const { - if (requestedPixels == 0) - return 0; - return 1.0f - - static_cast(usedPixels) / static_cast(requestedPixels); - } - - // 分割時の推定効率(0.0〜1.0): theoreticalMinPixels / requestedPixels - // 分割により理論上達成可能な効率(通常 ~50%) - float splitEfficiencyEstimate() const { - if (requestedPixels == 0) - return 1.0f; - return static_cast(theoreticalMinPixels) / - static_cast(requestedPixels); - } - - // メモリ確保を記録 - void recordAlloc(size_t bytes, int_fast16_t width, int_fast16_t height) { - allocatedBytes += static_cast(bytes); - allocCount++; - if (static_cast(bytes) > maxAllocBytes) { - maxAllocBytes = static_cast(bytes); - maxAllocWidth = static_cast(width); - maxAllocHeight = static_cast(height); + uint32_t time_us = 0; // 処理時間(マイクロ秒) + uint32_t count = 0; // 呼び出し回数 + uint32_t requestedPixels = 0; // 上流に要求したピクセル数 + uint32_t usedPixels = 0; // 実際に使用したピクセル数 + uint32_t theoreticalMinPixels = 0; // 理論最小ピクセル数(分割時の推定値) + uint32_t allocatedBytes = 0; // このノードが確保したバイト数 + uint32_t allocCount = 0; // 確保回数 + uint32_t maxAllocBytes = 0; // 一回の最大確保バイト数 + int16_t maxAllocWidth = 0; // その時の幅 + int16_t maxAllocHeight = 0; // その時の高さ + + void reset() + { + *this = NodeMetrics{}; + } + + // 現在のピクセル効率(0.0〜1.0): usedPixels / requestedPixels + float pixelEfficiency() const + { + if (requestedPixels == 0) return 1.0f; + return static_cast(usedPixels) / static_cast(requestedPixels); + } + + // 不要ピクセル率(0.0〜1.0) + float wasteRatio() const + { + if (requestedPixels == 0) return 0; + return 1.0f - static_cast(usedPixels) / static_cast(requestedPixels); + } + + // 分割時の推定効率(0.0〜1.0): theoreticalMinPixels / requestedPixels + // 分割により理論上達成可能な効率(通常 ~50%) + float splitEfficiencyEstimate() const + { + if (requestedPixels == 0) return 1.0f; + return static_cast(theoreticalMinPixels) / static_cast(requestedPixels); + } + + // メモリ確保を記録 + void recordAlloc(size_t bytes, int_fast16_t width, int_fast16_t height) + { + allocatedBytes += static_cast(bytes); + allocCount++; + if (static_cast(bytes) > maxAllocBytes) { + maxAllocBytes = static_cast(bytes); + maxAllocWidth = static_cast(width); + maxAllocHeight = static_cast(height); + } } - } }; struct PerfMetrics { - NodeMetrics nodes[NodeType::Count]; - - // グローバル統計(パイプライン全体) - uint32_t totalAllocatedBytes = 0; // 累計確保バイト数 - uint32_t peakMemoryBytes = 0; // ピークメモリ使用量 - uint32_t currentMemoryBytes = 0; // 現在のメモリ使用量 - uint32_t maxAllocBytes = 0; // 一回の最大確保バイト数 - int maxAllocWidth = 0; // その時の幅 - int maxAllocHeight = 0; // その時の高さ - - // シングルトンインスタンス - static PerfMetrics &instance() { - static PerfMetrics s_instance; - return s_instance; - } - - void reset() { - for (auto &n : nodes) - n.reset(); - totalAllocatedBytes = 0; - peakMemoryBytes = 0; - currentMemoryBytes = 0; - maxAllocBytes = 0; - maxAllocWidth = 0; - maxAllocHeight = 0; - } - - // 全ノード合計の処理時間(Renderer除外) - // Rendererはexec()全体時間を記録するため、合計から除外 - uint32_t totalTime() const { - uint32_t sum = 0; - for (int i = 0; i < NodeType::Count; ++i) { - if (i == NodeType::Renderer) - continue; // exec()全体時間は除外 - sum += nodes[i].time_us; + NodeMetrics nodes[NodeType::Count]; + + // グローバル統計(パイプライン全体) + uint32_t totalAllocatedBytes = 0; // 累計確保バイト数 + uint32_t peakMemoryBytes = 0; // ピークメモリ使用量 + uint32_t currentMemoryBytes = 0; // 現在のメモリ使用量 + uint32_t maxAllocBytes = 0; // 一回の最大確保バイト数 + int maxAllocWidth = 0; // その時の幅 + int maxAllocHeight = 0; // その時の高さ + + // シングルトンインスタンス + static PerfMetrics &instance() + { + static PerfMetrics s_instance; + return s_instance; + } + + void reset() + { + for (auto &n : nodes) n.reset(); + totalAllocatedBytes = 0; + peakMemoryBytes = 0; + currentMemoryBytes = 0; + maxAllocBytes = 0; + maxAllocWidth = 0; + maxAllocHeight = 0; } - return sum; - } - - // 全ノード合計の確保バイト数 - uint32_t totalNodeAllocatedBytes() const { - uint32_t sum = 0; - for (const auto &n : nodes) - sum += n.allocatedBytes; - return sum; - } - - // メモリ確保を記録(ImageBuffer作成時に呼ぶ) - void recordAlloc(size_t bytes, int width = 0, int height = 0) { - uint32_t b = static_cast(bytes); - totalAllocatedBytes += b; - currentMemoryBytes += b; - if (currentMemoryBytes > peakMemoryBytes) { - peakMemoryBytes = currentMemoryBytes; + + // 全ノード合計の処理時間(Renderer除外) + // Rendererはexec()全体時間を記録するため、合計から除外 + uint32_t totalTime() const + { + uint32_t sum = 0; + for (int i = 0; i < NodeType::Count; ++i) { + if (i == NodeType::Renderer) continue; // exec()全体時間は除外 + sum += nodes[i].time_us; + } + return sum; + } + + // 全ノード合計の確保バイト数 + uint32_t totalNodeAllocatedBytes() const + { + uint32_t sum = 0; + for (const auto &n : nodes) sum += n.allocatedBytes; + return sum; } - if (b > maxAllocBytes) { - maxAllocBytes = b; - maxAllocWidth = width; - maxAllocHeight = height; + + // メモリ確保を記録(ImageBuffer作成時に呼ぶ) + void recordAlloc(size_t bytes, int width = 0, int height = 0) + { + uint32_t b = static_cast(bytes); + totalAllocatedBytes += b; + currentMemoryBytes += b; + if (currentMemoryBytes > peakMemoryBytes) { + peakMemoryBytes = currentMemoryBytes; + } + if (b > maxAllocBytes) { + maxAllocBytes = b; + maxAllocWidth = width; + maxAllocHeight = height; + } } - } - - // メモリ解放を記録(ImageBuffer破棄時に呼ぶ) - void recordFree(size_t bytes) { - uint32_t b = static_cast(bytes); - if (currentMemoryBytes >= b) { - currentMemoryBytes -= b; - } else { - currentMemoryBytes = 0; + + // メモリ解放を記録(ImageBuffer破棄時に呼ぶ) + void recordFree(size_t bytes) + { + uint32_t b = static_cast(bytes); + if (currentMemoryBytes >= b) { + currentMemoryBytes -= b; + } else { + currentMemoryBytes = 0; + } } - } }; // ======================================================================== @@ -217,77 +222,97 @@ struct PerfMetrics { // class MetricsGuard { public: - explicit MetricsGuard(int nodeType) - : nodeType_(nodeType) + explicit MetricsGuard(int nodeType) + : nodeType_(nodeType) #ifdef ESP32 - , - start_(micros()) + , + start_(micros()) #else - , - start_(std::chrono::high_resolution_clock::now()) + , + start_(std::chrono::high_resolution_clock::now()) #endif - { - } + { + } - ~MetricsGuard() { + ~MetricsGuard() + { #ifdef ESP32 - uint32_t elapsed = micros() - start_; + uint32_t elapsed = micros() - start_; #else - auto elapsed = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - start_) - .count(); + auto elapsed = + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_) + .count(); #endif - auto &metrics = PerfMetrics::instance().nodes[nodeType_]; - metrics.time_us += static_cast(elapsed); - metrics.count++; - } + auto &metrics = PerfMetrics::instance().nodes[nodeType_]; + metrics.time_us += static_cast(elapsed); + metrics.count++; + } - // コピー・ムーブ禁止 - MetricsGuard(const MetricsGuard &) = delete; - MetricsGuard &operator=(const MetricsGuard &) = delete; - MetricsGuard(MetricsGuard &&) = delete; - MetricsGuard &operator=(MetricsGuard &&) = delete; + // コピー・ムーブ禁止 + MetricsGuard(const MetricsGuard &) = delete; + MetricsGuard &operator=(const MetricsGuard &) = delete; + MetricsGuard(MetricsGuard &&) = delete; + MetricsGuard &operator=(MetricsGuard &&) = delete; private: - int nodeType_; + int nodeType_; #ifdef ESP32 - uint32_t start_; + uint32_t start_; #else - std::chrono::high_resolution_clock::time_point start_; + std::chrono::high_resolution_clock::time_point start_; #endif }; -#define FLEXIMG_METRICS_SCOPE(nodeType) \ - ::FLEXIMG_NAMESPACE::core::MetricsGuard _metricsGuard##__LINE__(nodeType) +#define FLEXIMG_METRICS_SCOPE(nodeType) ::FLEXIMG_NAMESPACE::core::MetricsGuard _metricsGuard##__LINE__(nodeType) #else // リリースビルド用のダミー構造体(最小サイズ) struct NodeMetrics { - void reset() {} - float wasteRatio() const { return 0; } - void recordAlloc(size_t, int, int) {} + void reset() + { + } + float wasteRatio() const + { + return 0; + } + void recordAlloc(size_t, int, int) + { + } }; struct PerfMetrics { - NodeMetrics nodes[NodeType::Count]; - static PerfMetrics &instance() { - static PerfMetrics s_instance; - return s_instance; - } - void reset() {} - uint32_t totalTime() const { return 0; } - uint32_t totalNodeAllocatedBytes() const { return 0; } - void recordAlloc(size_t, int = 0, int = 0) {} - void recordFree(size_t) {} + NodeMetrics nodes[NodeType::Count]; + static PerfMetrics &instance() + { + static PerfMetrics s_instance; + return s_instance; + } + void reset() + { + } + uint32_t totalTime() const + { + return 0; + } + uint32_t totalNodeAllocatedBytes() const + { + return 0; + } + void recordAlloc(size_t, int = 0, int = 0) + { + } + void recordFree(size_t) + { + } }; // リリースビルド用: メトリクス計測マクロは何もしない #define FLEXIMG_METRICS_SCOPE(nodeType) ((void)0) -#endif // FLEXIMG_DEBUG_PERF_METRICS +#endif // FLEXIMG_DEBUG_PERF_METRICS -} // namespace core +} // namespace core // ======================================================================== // 後方互換性のためのグローバルスコープ using(v3.0 で削除予定) @@ -308,6 +333,6 @@ namespace NodeType = core::NodeType; using core::NodeMetrics; using core::PerfMetrics; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_PERF_METRICS_H +#endif // FLEXIMG_PERF_METRICS_H diff --git a/src/fleximg/core/port.h b/src/fleximg/core/port.h index 03a2342..3b544b8 100644 --- a/src/fleximg/core/port.h +++ b/src/fleximg/core/port.h @@ -19,40 +19,50 @@ class Node; // struct Port { - Node *owner = nullptr; // このポートを所有するノード - Port *connected = nullptr; // 接続先ポート(nullptr = 未接続) - int index = 0; // ノード内でのポート番号 + Node *owner = nullptr; // このポートを所有するノード + Port *connected = nullptr; // 接続先ポート(nullptr = 未接続) + int index = 0; // ノード内でのポート番号 - Port() = default; - Port(Node *own, int idx) : owner(own), index(idx) {} + Port() = default; + Port(Node *own, int idx) : owner(own), index(idx) + { + } - // 接続状態 - bool isConnected() const { return connected != nullptr; } + // 接続状態 + bool isConnected() const + { + return connected != nullptr; + } - // 接続(相互参照を設定) - // 戻り値: 成功=true, 既に接続済み=false - bool connect(Port &other) { - if (connected || other.connected) { - return false; // どちらかが既に接続済み + // 接続(相互参照を設定) + // 戻り値: 成功=true, 既に接続済み=false + bool connect(Port &other) + { + if (connected || other.connected) { + return false; // どちらかが既に接続済み + } + connected = &other; + other.connected = this; + return true; } - connected = &other; - other.connected = this; - return true; - } - - // 切断 - void disconnect() { - if (connected) { - connected->connected = nullptr; - connected = nullptr; + + // 切断 + void disconnect() + { + if (connected) { + connected->connected = nullptr; + connected = nullptr; + } } - } - // 接続先ノードを取得 - Node *connectedNode() const { return connected ? connected->owner : nullptr; } + // 接続先ノードを取得 + Node *connectedNode() const + { + return connected ? connected->owner : nullptr; + } }; -} // namespace core -} // namespace FLEXIMG_NAMESPACE +} // namespace core +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_PORT_H +#endif // FLEXIMG_PORT_H diff --git a/src/fleximg/core/render_context.h b/src/fleximg/core/render_context.h index adf83e8..970ac3d 100644 --- a/src/fleximg/core/render_context.h +++ b/src/fleximg/core/render_context.h @@ -37,173 +37,186 @@ namespace core { class RenderContext { public: - /// @brief RenderResponseプールサイズ(ImageBufferEntryPoolと同様の管理) - static constexpr int MAX_RESPONSES_BITS = 3; // 2^3 = 8 - static constexpr int MAX_RESPONSES = 1 << MAX_RESPONSES_BITS; - - /// @brief エラー種別 - enum class Error { - None = 0, - PoolExhausted, // プール枯渇 - ResponseNotReturned, // 未返却検出 - }; - - RenderContext() = default; - - // ======================================== - // アクセサ - // ======================================== - - /// @brief アロケータを取得 - memory::IAllocator *allocator() const { return allocator_; } - - /// @brief エントリプールを取得 - ImageBufferEntryPool *entryPool() const { return entryPool_; } - - // ======================================== - // RendererNode用設定メソッド - // ======================================== - - /// @brief アロケータとエントリプールを一括設定 - /// @param alloc メモリアロケータ - /// @param pool エントリプール - void setup(memory::IAllocator *alloc, ImageBufferEntryPool *pool) { - allocator_ = alloc; - entryPool_ = pool; - for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { - responsePool_[i].setAllocator(allocator_); - responsePool_[i].setPool(entryPool_); + /// @brief RenderResponseプールサイズ(ImageBufferEntryPoolと同様の管理) + static constexpr int MAX_RESPONSES_BITS = 3; // 2^3 = 8 + static constexpr int MAX_RESPONSES = 1 << MAX_RESPONSES_BITS; + + /// @brief エラー種別 + enum class Error { + None = 0, + PoolExhausted, // プール枯渇 + ResponseNotReturned, // 未返却検出 + }; + + RenderContext() = default; + + // ======================================== + // アクセサ + // ======================================== + + /// @brief アロケータを取得 + memory::IAllocator *allocator() const + { + return allocator_; } - } - - // ======================================== - // ValidSegmentsプール(バンプアロケータ) - // ======================================== - - /// @brief セグメント領域を確保 - /// @param count 必要なDataRangeスロット数 - /// @return 確保した領域の先頭ポインタ(枯渇時はnullptr) - /// @note スキャンラインスコープ。resetScanlineResources()で一括解放 - DataRange *acquireSegments(int count) { - if (segmentOffset_ + count > SEGMENT_POOL_SIZE) - return nullptr; - DataRange *result = &segmentStorage_[segmentOffset_]; - segmentOffset_ += count; - return result; - } - - // ======================================== - // RenderResponse貸出API(ImageBufferEntryPool方式) - // ======================================== - - /// @brief RenderResponseを取得(借用) - /// @return RenderResponse参照(pool/allocatorはsetupで設定済み) - /// @note プール枯渇時はエラーフラグを設定し、フォールバックを返す - /// @note ヒント付き循環探索でO(1)に近い性能を実現 - RenderResponse &acquireResponse() { - // nextHint_から開始して循環探索 - uint_fast8_t idx = nextHint_; - for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { - idx = (idx + 1) & (MAX_RESPONSES - 1); - if (!responsePool_[idx].inUse) { - responsePool_[idx].inUse = true; - nextHint_ = idx; - return responsePool_[idx]; - } + + /// @brief エントリプールを取得 + ImageBufferEntryPool *entryPool() const + { + return entryPool_; } - // プール枯渇 - error_ = Error::PoolExhausted; - FLEXIMG_DEBUG_WARN("ERROR: RenderResponse pool exhausted! MAX=%d", - MAX_RESPONSES); - // フォールバック: 最後のエントリを強制再利用(エラー状態) - RenderResponse &fallback = responsePool_[MAX_RESPONSES - 1]; - fallback.setPool(entryPool_); - fallback.setAllocator(allocator_); - fallback.clear(); - return fallback; - } - - /// @brief RenderResponseを返却 - /// @param resp 返却するResponse参照 - /// @note ImageBufferEntryPoolと同様の範囲チェック付き - void releaseResponse(RenderResponse &resp) { - // 範囲チェック(プール内のアドレスか確認) - size_t idx = static_cast(&resp - responsePool_); - if (idx < MAX_RESPONSES) { - if (!resp.inUse) { - FLEXIMG_DEBUG_WARN( - "WARN: releaseResponse called on non-inUse response idx=%d", - static_cast(idx)); - } - resp.clear(); // エントリをプールに返却 - resp.inUse = false; // スロットを再利用可能に + + // ======================================== + // RendererNode用設定メソッド + // ======================================== + + /// @brief アロケータとエントリプールを一括設定 + /// @param alloc メモリアロケータ + /// @param pool エントリプール + void setup(memory::IAllocator *alloc, ImageBufferEntryPool *pool) + { + allocator_ = alloc; + entryPool_ = pool; + for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { + responsePool_[i].setAllocator(allocator_); + responsePool_[i].setPool(entryPool_); + } } - } - /// @brief 全RenderResponseを一括解放(フレーム終了時) - /// @note ImageBufferEntryPool::releaseAll()と同様 - void resetScanlineResources() { -#ifdef FLEXIMG_DEBUG - // 未返却チェック - // 1つは下流に渡されるため、1以下なら正常 + // ======================================== + // ValidSegmentsプール(バンプアロケータ) + // ======================================== + + /// @brief セグメント領域を確保 + /// @param count 必要なDataRangeスロット数 + /// @return 確保した領域の先頭ポインタ(枯渇時はnullptr) + /// @note スキャンラインスコープ。resetScanlineResources()で一括解放 + DataRange *acquireSegments(int count) + { + if (segmentOffset_ + count > SEGMENT_POOL_SIZE) return nullptr; + DataRange *result = &segmentStorage_[segmentOffset_]; + segmentOffset_ += count; + return result; + } + + // ======================================== + // RenderResponse貸出API(ImageBufferEntryPool方式) + // ======================================== + + /// @brief RenderResponseを取得(借用) + /// @return RenderResponse参照(pool/allocatorはsetupで設定済み) + /// @note プール枯渇時はエラーフラグを設定し、フォールバックを返す + /// @note ヒント付き循環探索でO(1)に近い性能を実現 + RenderResponse &acquireResponse() { - uint_fast8_t inUseCount = 0; - for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { - if (responsePool_[i].inUse) - ++inUseCount; - } - if (inUseCount > 1) { - FLEXIMG_DEBUG_WARN( - "WARN: resetScanlineResources with %d responses still in use", - inUseCount); - } + // nextHint_から開始して循環探索 + uint_fast8_t idx = nextHint_; + for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { + idx = (idx + 1) & (MAX_RESPONSES - 1); + if (!responsePool_[idx].inUse) { + responsePool_[idx].inUse = true; + nextHint_ = idx; + return responsePool_[idx]; + } + } + // プール枯渇 + error_ = Error::PoolExhausted; + FLEXIMG_DEBUG_WARN("ERROR: RenderResponse pool exhausted! MAX=%d", MAX_RESPONSES); + // フォールバック: 最後のエントリを強制再利用(エラー状態) + RenderResponse &fallback = responsePool_[MAX_RESPONSES - 1]; + fallback.setPool(entryPool_); + fallback.setAllocator(allocator_); + fallback.clear(); + return fallback; } + + /// @brief RenderResponseを返却 + /// @param resp 返却するResponse参照 + /// @note ImageBufferEntryPoolと同様の範囲チェック付き + void releaseResponse(RenderResponse &resp) + { + // 範囲チェック(プール内のアドレスか確認) + size_t idx = static_cast(&resp - responsePool_); + if (idx < MAX_RESPONSES) { + if (!resp.inUse) { + FLEXIMG_DEBUG_WARN("WARN: releaseResponse called on non-inUse response idx=%d", static_cast(idx)); + } + resp.clear(); // エントリをプールに返却 + resp.inUse = false; // スロットを再利用可能に + } + } + + /// @brief 全RenderResponseを一括解放(フレーム終了時) + /// @note ImageBufferEntryPool::releaseAll()と同様 + void resetScanlineResources() + { +#ifdef FLEXIMG_DEBUG + // 未返却チェック + // 1つは下流に渡されるため、1以下なら正常 + { + uint_fast8_t inUseCount = 0; + for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { + if (responsePool_[i].inUse) ++inUseCount; + } + if (inUseCount > 1) { + FLEXIMG_DEBUG_WARN("WARN: resetScanlineResources with %d responses still in use", inUseCount); + } + } #endif - for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { - if (responsePool_[i].inUse) { - responsePool_[i].clear(); - responsePool_[i].inUse = false; - } + for (uint_fast8_t i = 0; i < MAX_RESPONSES; ++i) { + if (responsePool_[i].inUse) { + responsePool_[i].clear(); + responsePool_[i].inUse = false; + } + } + nextHint_ = 0; + segmentOffset_ = 0; } - nextHint_ = 0; - segmentOffset_ = 0; - } - // ======================================== - // エラー管理 - // ======================================== + // ======================================== + // エラー管理 + // ======================================== - /// @brief エラーがあるか確認 - bool hasError() const { return error_ != Error::None; } + /// @brief エラーがあるか確認 + bool hasError() const + { + return error_ != Error::None; + } - /// @brief エラー種別を取得 - Error error() const { return error_; } + /// @brief エラー種別を取得 + Error error() const + { + return error_; + } - /// @brief エラーをクリア - void clearError() { error_ = Error::None; } + /// @brief エラーをクリア + void clearError() + { + error_ = Error::None; + } private: - memory::IAllocator *allocator_ = nullptr; - ImageBufferEntryPool *entryPool_ = nullptr; - - // RenderResponseプール(ImageBufferEntryPoolと同様の管理) - RenderResponse responsePool_[MAX_RESPONSES]; - Error error_ = Error::None; - uint_fast8_t nextHint_ = 0; // 次回探索開始位置(循環探索用) - - // ValidSegmentsプール(バンプアロケータ) - // CompositeNode等がImageBufferのvalidSegments追跡用に借用する - // スキャンラインスコープで一括解放(resetScanlineResources) - static constexpr int SEGMENT_POOL_SIZE = 256; - DataRange segmentStorage_[SEGMENT_POOL_SIZE]; - int_fast16_t segmentOffset_ = 0; + memory::IAllocator *allocator_ = nullptr; + ImageBufferEntryPool *entryPool_ = nullptr; + + // RenderResponseプール(ImageBufferEntryPoolと同様の管理) + RenderResponse responsePool_[MAX_RESPONSES]; + Error error_ = Error::None; + uint_fast8_t nextHint_ = 0; // 次回探索開始位置(循環探索用) + + // ValidSegmentsプール(バンプアロケータ) + // CompositeNode等がImageBufferのvalidSegments追跡用に借用する + // スキャンラインスコープで一括解放(resetScanlineResources) + static constexpr int SEGMENT_POOL_SIZE = 256; + DataRange segmentStorage_[SEGMENT_POOL_SIZE]; + int_fast16_t segmentOffset_ = 0; }; -} // namespace core +} // namespace core // 親名前空間に公開 using core::RenderContext; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_RENDER_CONTEXT_H +#endif // FLEXIMG_RENDER_CONTEXT_H diff --git a/src/fleximg/core/types.h b/src/fleximg/core/types.h index 6290198..03a115c 100644 --- a/src/fleximg/core/types.h +++ b/src/fleximg/core/types.h @@ -24,9 +24,9 @@ namespace core { using int_fixed = int32_t; -constexpr int INT_FIXED_SHIFT = 16; -constexpr int_fixed INT_FIXED_ONE = 1 << INT_FIXED_SHIFT; // 65536 -constexpr int_fixed INT_FIXED_HALF = 1 << (INT_FIXED_SHIFT - 1); // 32768 +constexpr int INT_FIXED_SHIFT = 16; +constexpr int_fixed INT_FIXED_ONE = 1 << INT_FIXED_SHIFT; // 65536 +constexpr int_fixed INT_FIXED_HALF = 1 << (INT_FIXED_SHIFT - 1); // 32768 // ======================================================================== // 2x2 行列テンプレート @@ -44,13 +44,17 @@ constexpr int_fixed INT_FIXED_HALF = 1 << (INT_FIXED_SHIFT - 1); // 32768 // - matrix_: 順行列 // -template struct Matrix2x2 { - T a, b, c, d; - bool valid = false; - - Matrix2x2() : a(0), b(0), c(0), d(0), valid(false) {} - Matrix2x2(T a_, T b_, T c_, T d_, bool v = true) - : a(a_), b(b_), c(c_), d(d_), valid(v) {} +template +struct Matrix2x2 { + T a, b, c, d; + bool valid = false; + + Matrix2x2() : a(0), b(0), c(0), d(0), valid(false) + { + } + Matrix2x2(T a_, T b_, T c_, T d_, bool v = true) : a(a_), b(b_), c(c_), d(d_), valid(v) + { + } }; // 精度別エイリアス @@ -61,25 +65,38 @@ using Matrix2x2_fixed = Matrix2x2; // ======================================================================== struct Point { - int_fixed x = 0; - int_fixed y = 0; - - Point() = default; - Point(int_fixed x_, int_fixed y_) : x(x_), y(y_) {} - - Point operator+(const Point &o) const { return {x + o.x, y + o.y}; } - Point operator-(const Point &o) const { return {x - o.x, y - o.y}; } - Point operator-() const { return {-x, -y}; } - Point &operator+=(const Point &o) { - x += o.x; - y += o.y; - return *this; - } - Point &operator-=(const Point &o) { - x -= o.x; - y -= o.y; - return *this; - } + int_fixed x = 0; + int_fixed y = 0; + + Point() = default; + Point(int_fixed x_, int_fixed y_) : x(x_), y(y_) + { + } + + Point operator+(const Point &o) const + { + return {x + o.x, y + o.y}; + } + Point operator-(const Point &o) const + { + return {x - o.x, y - o.y}; + } + Point operator-() const + { + return {-x, -y}; + } + Point &operator+=(const Point &o) + { + x += o.x; + y += o.y; + return *this; + } + Point &operator-=(const Point &o) + { + x -= o.x; + y -= o.y; + return *this; + } }; // ======================================================================== @@ -91,43 +108,54 @@ struct Point { // ------------------------------------------------------------------------ // int → fixed -constexpr int_fixed to_fixed(int v) { - return static_cast(v) << INT_FIXED_SHIFT; +constexpr int_fixed to_fixed(int v) +{ + return static_cast(v) << INT_FIXED_SHIFT; } // fixed → int (floor: 負の無限大方向への丸め) // 算術右シフトにより、常に負の無限大方向へ丸められる // 例: 10.7 → 10, 10.3 → 10, -10.3 → -11, -10.7 → -11 -constexpr int from_fixed_floor(int_fixed v) { return v >> INT_FIXED_SHIFT; } +constexpr int from_fixed_floor(int_fixed v) +{ + return v >> INT_FIXED_SHIFT; +} // fixed → int (ceil: 正の無限大方向への丸め) // 例: 10.3 → 11, 10.0 → 10, -10.7 → -10, -10.0 → -10 -constexpr int from_fixed_ceil(int_fixed v) { - return (v + INT_FIXED_ONE - 1) >> INT_FIXED_SHIFT; +constexpr int from_fixed_ceil(int_fixed v) +{ + return (v + INT_FIXED_ONE - 1) >> INT_FIXED_SHIFT; } // fixed → int (round: 四捨五入、round half up) // 0.5以上で切り上げ、0.5未満で切り捨て // 例: 10.5 → 11, 10.4 → 10, -10.4 → -10, -10.5 → -10, -10.6 → -11 -constexpr int from_fixed_round(int_fixed v) { - return (v + INT_FIXED_HALF) >> INT_FIXED_SHIFT; +constexpr int from_fixed_round(int_fixed v) +{ + return (v + INT_FIXED_HALF) >> INT_FIXED_SHIFT; } // 互換性のためのエイリアス(from_fixed_floor と同じ) -constexpr int from_fixed(int_fixed v) { return from_fixed_floor(v); } +constexpr int from_fixed(int_fixed v) +{ + return from_fixed_floor(v); +} // ------------------------------------------------------------------------ // float ↔ fixed 変換 // ------------------------------------------------------------------------ // float → fixed -constexpr int_fixed float_to_fixed(float v) { - return static_cast(v * INT_FIXED_ONE); +constexpr int_fixed float_to_fixed(float v) +{ + return static_cast(v * INT_FIXED_ONE); } // fixed → float -constexpr float fixed_to_float(int_fixed v) { - return static_cast(v) / INT_FIXED_ONE; +constexpr float fixed_to_float(int_fixed v) +{ + return static_cast(v) / INT_FIXED_ONE; } // ======================================================================== @@ -135,15 +163,15 @@ constexpr float fixed_to_float(int_fixed v) { // ======================================================================== // fixed 同士の乗算 (結果も fixed) -constexpr int_fixed mul_fixed(int_fixed a, int_fixed b) { - return static_cast((static_cast(a) * b) >> - INT_FIXED_SHIFT); +constexpr int_fixed mul_fixed(int_fixed a, int_fixed b) +{ + return static_cast((static_cast(a) * b) >> INT_FIXED_SHIFT); } // fixed 同士の除算 (結果も fixed) -constexpr int_fixed div_fixed(int_fixed a, int_fixed b) { - return static_cast((static_cast(a) << INT_FIXED_SHIFT) / - b); +constexpr int_fixed div_fixed(int_fixed a, int_fixed b) +{ + return static_cast((static_cast(a) << INT_FIXED_SHIFT) / b); } // ======================================================================== @@ -151,36 +179,48 @@ constexpr int_fixed div_fixed(int_fixed a, int_fixed b) { // ======================================================================== struct AffineMatrix { - float a = 1, b = 0; // | a b tx | - float c = 0, d = 1; // | c d ty | - float tx = 0, ty = 0; - - AffineMatrix() = default; - AffineMatrix(float a_, float b_, float c_, float d_, float tx_, float ty_) - : a(a_), b(b_), c(c_), d(d_), tx(tx_), ty(ty_) {} - - // 単位行列 - static AffineMatrix identity() { return {1, 0, 0, 1, 0, 0}; } - - // 平行移動 - static AffineMatrix translate(float x, float y) { return {1, 0, 0, 1, x, y}; } - - // スケール - static AffineMatrix scale(float sx, float sy) { return {sx, 0, 0, sy, 0, 0}; } - - // 回転(ラジアン) - static AffineMatrix rotate(float radians); - - // 行列の乗算(合成): this * other - AffineMatrix operator*(const AffineMatrix &other) const { - return AffineMatrix(a * other.a + b * other.c, // a - a * other.b + b * other.d, // b - c * other.a + d * other.c, // c - c * other.b + d * other.d, // d - a * other.tx + b * other.ty + tx, // tx - c * other.tx + d * other.ty + ty // ty - ); - } + float a = 1, b = 0; // | a b tx | + float c = 0, d = 1; // | c d ty | + float tx = 0, ty = 0; + + AffineMatrix() = default; + AffineMatrix(float a_, float b_, float c_, float d_, float tx_, float ty_) + : a(a_), b(b_), c(c_), d(d_), tx(tx_), ty(ty_) + { + } + + // 単位行列 + static AffineMatrix identity() + { + return {1, 0, 0, 1, 0, 0}; + } + + // 平行移動 + static AffineMatrix translate(float x, float y) + { + return {1, 0, 0, 1, x, y}; + } + + // スケール + static AffineMatrix scale(float sx, float sy) + { + return {sx, 0, 0, sy, 0, 0}; + } + + // 回転(ラジアン) + static AffineMatrix rotate(float radians); + + // 行列の乗算(合成): this * other + AffineMatrix operator*(const AffineMatrix &other) const + { + return AffineMatrix(a * other.a + b * other.c, // a + a * other.b + b * other.d, // b + c * other.a + d * other.c, // c + c * other.b + d * other.d, // d + a * other.tx + b * other.ty + tx, // tx + c * other.tx + d * other.ty + ty // ty + ); + } }; // ======================================================================== @@ -189,32 +229,32 @@ struct AffineMatrix { // AffineMatrix の 2x2 部分を固定小数点で返す(順変換用) // 平行移動成分(tx,ty)は含まない(呼び出し側で別途管理) -inline Matrix2x2_fixed toFixed(const AffineMatrix &m) { - return Matrix2x2_fixed( - static_cast(std::lround(m.a * INT_FIXED_ONE)), - static_cast(std::lround(m.b * INT_FIXED_ONE)), - static_cast(std::lround(m.c * INT_FIXED_ONE)), - static_cast(std::lround(m.d * INT_FIXED_ONE)), - true // valid - ); +inline Matrix2x2_fixed toFixed(const AffineMatrix &m) +{ + return Matrix2x2_fixed(static_cast(std::lround(m.a * INT_FIXED_ONE)), + static_cast(std::lround(m.b * INT_FIXED_ONE)), + static_cast(std::lround(m.c * INT_FIXED_ONE)), + static_cast(std::lround(m.d * INT_FIXED_ONE)), + true // valid + ); } // AffineMatrix の 2x2 部分の逆行列を固定小数点で返す(逆変換用) // 平行移動成分(tx,ty)は含まない(呼び出し側で別途管理) -inline Matrix2x2_fixed inverseFixed(const AffineMatrix &m) { - float det = m.a * m.d - m.b * m.c; - if (std::abs(det) < 1e-10f) { - return Matrix2x2_fixed(); // valid = false - } - - float invDet = 1.0f / det; - return Matrix2x2_fixed( - static_cast(std::lround(m.d * invDet * INT_FIXED_ONE)), - static_cast(std::lround(-m.b * invDet * INT_FIXED_ONE)), - static_cast(std::lround(-m.c * invDet * INT_FIXED_ONE)), - static_cast(std::lround(m.a * invDet * INT_FIXED_ONE)), - true // valid - ); +inline Matrix2x2_fixed inverseFixed(const AffineMatrix &m) +{ + float det = m.a * m.d - m.b * m.c; + if (std::abs(det) < 1e-10f) { + return Matrix2x2_fixed(); // valid = false + } + + float invDet = 1.0f / det; + return Matrix2x2_fixed(static_cast(std::lround(m.d * invDet * INT_FIXED_ONE)), + static_cast(std::lround(-m.b * invDet * INT_FIXED_ONE)), + static_cast(std::lround(-m.c * invDet * INT_FIXED_ONE)), + static_cast(std::lround(m.a * invDet * INT_FIXED_ONE)), + true // valid + ); } // ======================================================================== @@ -227,51 +267,55 @@ inline Matrix2x2_fixed inverseFixed(const AffineMatrix &m) { // struct AffinePrecomputed { - Matrix2x2_fixed invMatrix; // 逆行列(2x2部分) - int_fixed invTxFixed = 0; // 逆変換オフセットX(Q16.16) - int_fixed invTyFixed = 0; // 逆変換オフセットY(Q16.16) - int_fixed rowOffsetX = 0; // ピクセル中心オフセット: invMatrix.b >> 1 - int_fixed rowOffsetY = 0; // ピクセル中心オフセット: invMatrix.d >> 1 - int_fixed dxOffsetX = 0; // ピクセル中心オフセット: invMatrix.a >> 1 - int_fixed dxOffsetY = 0; // ピクセル中心オフセット: invMatrix.c >> 1 - - bool isValid() const { return invMatrix.valid; } + Matrix2x2_fixed invMatrix; // 逆行列(2x2部分) + int_fixed invTxFixed = 0; // 逆変換オフセットX(Q16.16) + int_fixed invTyFixed = 0; // 逆変換オフセットY(Q16.16) + int_fixed rowOffsetX = 0; // ピクセル中心オフセット: invMatrix.b >> 1 + int_fixed rowOffsetY = 0; // ピクセル中心オフセット: invMatrix.d >> 1 + int_fixed dxOffsetX = 0; // ピクセル中心オフセット: invMatrix.a >> 1 + int_fixed dxOffsetY = 0; // ピクセル中心オフセット: invMatrix.c >> 1 + + bool isValid() const + { + return invMatrix.valid; + } }; // アフィン行列から事前計算値を生成 // 逆行列、逆変換オフセット、ピクセル中心オフセットを計算 -inline AffinePrecomputed precomputeInverseAffine(const AffineMatrix &m) { - AffinePrecomputed result; - - // 逆行列を計算 - result.invMatrix = inverseFixed(m); - if (!result.invMatrix.valid) { - return result; // 特異行列の場合は無効な結果を返す - } - - // tx/ty を Q16.16 固定小数点に変換 - int_fixed txFixed = float_to_fixed(m.tx); - int_fixed tyFixed = float_to_fixed(m.ty); - - // 逆変換オフセットの計算(tx/ty と逆行列から) - // Q16.16 × Q16.16 = Q32、16bit シフトで Q16.16 - int64_t invTx64 = -(static_cast(txFixed) * result.invMatrix.a + - static_cast(tyFixed) * result.invMatrix.b); - int64_t invTy64 = -(static_cast(txFixed) * result.invMatrix.c + - static_cast(tyFixed) * result.invMatrix.d); - result.invTxFixed = static_cast(invTx64 >> INT_FIXED_SHIFT); - result.invTyFixed = static_cast(invTy64 >> INT_FIXED_SHIFT); - - // ピクセル中心オフセット - result.rowOffsetX = result.invMatrix.b >> 1; - result.rowOffsetY = result.invMatrix.d >> 1; - result.dxOffsetX = result.invMatrix.a >> 1; - result.dxOffsetY = result.invMatrix.c >> 1; - - return result; +inline AffinePrecomputed precomputeInverseAffine(const AffineMatrix &m) +{ + AffinePrecomputed result; + + // 逆行列を計算 + result.invMatrix = inverseFixed(m); + if (!result.invMatrix.valid) { + return result; // 特異行列の場合は無効な結果を返す + } + + // tx/ty を Q16.16 固定小数点に変換 + int_fixed txFixed = float_to_fixed(m.tx); + int_fixed tyFixed = float_to_fixed(m.ty); + + // 逆変換オフセットの計算(tx/ty と逆行列から) + // Q16.16 × Q16.16 = Q32、16bit シフトで Q16.16 + int64_t invTx64 = + -(static_cast(txFixed) * result.invMatrix.a + static_cast(tyFixed) * result.invMatrix.b); + int64_t invTy64 = + -(static_cast(txFixed) * result.invMatrix.c + static_cast(tyFixed) * result.invMatrix.d); + result.invTxFixed = static_cast(invTx64 >> INT_FIXED_SHIFT); + result.invTyFixed = static_cast(invTy64 >> INT_FIXED_SHIFT); + + // ピクセル中心オフセット + result.rowOffsetX = result.invMatrix.b >> 1; + result.rowOffsetY = result.invMatrix.d >> 1; + result.dxOffsetX = result.invMatrix.a >> 1; + result.dxOffsetY = result.invMatrix.c >> 1; + + return result; } -} // namespace core +} // namespace core // ======================================================================== // 後方互換性のためのグローバルスコープ using(v3.0 で削除予定) @@ -312,6 +356,6 @@ using core::precomputeInverseAffine; using core::to_fixed; using core::toFixed; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_TYPES_H +#endif // FLEXIMG_TYPES_H diff --git a/src/fleximg/image/data_range.h b/src/fleximg/image/data_range.h index 6d2c06c..9964f64 100644 --- a/src/fleximg/image/data_range.h +++ b/src/fleximg/image/data_range.h @@ -15,13 +15,19 @@ namespace FLEXIMG_NAMESPACE { // struct DataRange { - int16_t startX = 0; // 有効開始X(request座標系) - int16_t endX = 0; // 有効終了X(request座標系) + int16_t startX = 0; // 有効開始X(request座標系) + int16_t endX = 0; // 有効終了X(request座標系) - bool hasData() const { return startX < endX; } - int16_t width() const { return (startX < endX) ? (endX - startX) : 0; } + bool hasData() const + { + return startX < endX; + } + int16_t width() const + { + return (startX < endX) ? (endX - startX) : 0; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_DATA_RANGE_H +#endif // FLEXIMG_DATA_RANGE_H diff --git a/src/fleximg/image/image_buffer.h b/src/fleximg/image/image_buffer.h index 06399a3..97154a3 100644 --- a/src/fleximg/image/image_buffer.h +++ b/src/fleximg/image/image_buffer.h @@ -19,9 +19,9 @@ namespace FLEXIMG_NAMESPACE { // InitPolicy - ImageBuffer初期化ポリシー // ======================================================================== enum class InitPolicy : uint8_t { - Zero, // ゼロクリア - Uninitialized, // 初期化スキップ(全ピクセル上書き時に使用) - DebugPattern // デバッグ用パターン値で埋める(未初期化使用の検出用) + Zero, // ゼロクリア + Uninitialized, // 初期化スキップ(全ピクセル上書き時に使用) + DebugPattern // デバッグ用パターン値で埋める(未初期化使用の検出用) }; // デフォルト初期化ポリシー @@ -37,8 +37,8 @@ constexpr InitPolicy DefaultInitPolicy = InitPolicy::DebugPattern; // FormatConversion - toFormat()の変換モード // ======================================================================== enum class FormatConversion : uint8_t { - CopyIfNeeded, // デフォルト: 参照モードならコピー作成 - PreferReference // 編集しない: フォーマット一致なら参照のまま返す + CopyIfNeeded, // デフォルト: 参照モードならコピー作成 + PreferReference // 編集しない: フォーマット一致なら参照のまま返す }; // ======================================================================== @@ -53,488 +53,564 @@ enum class FormatConversion : uint8_t { class ImageBuffer { public: - // ======================================== - // コンストラクタ / デストラクタ - // ======================================== - - // デフォルトコンストラクタ(空の画像) - ImageBuffer() - : view_(), capacity_(0), - allocator_(&core::memory::DefaultAllocator::instance()), auxInfo_(), - origin_(), initPolicy_(DefaultInitPolicy) {} - - // サイズ指定コンストラクタ - // alloc = nullptr の場合、DefaultAllocator を使用 - ImageBuffer(int_fast16_t w, int_fast16_t h, - PixelFormatID fmt = PixelFormatIDs::RGBA8_Straight, - InitPolicy init = DefaultInitPolicy, - core::memory::IAllocator *alloc = nullptr) - : view_(nullptr, fmt, 0, static_cast(w), - static_cast(h)), - capacity_(0), - allocator_(alloc ? alloc : &core::memory::DefaultAllocator::instance()), - auxInfo_(), origin_(), initPolicy_(init) { - allocate(); - } - - // 外部ViewPortを参照(メモリ所有しない) - // 使用例: ImageBuffer ref(someViewPort); - explicit ImageBuffer(ViewPort view) - : view_(view), capacity_(0), - allocator_(nullptr) // nullなのでデストラクタで解放しない - , - auxInfo_(), origin_(), initPolicy_(InitPolicy::Zero) {} - - // デストラクタ - ~ImageBuffer() { deallocate(); } - - // ======================================== - // コピー / ムーブセマンティクス - // ======================================== - - // コピーコンストラクタ(ディープコピー) - // 参照モードからのコピーでも新しいメモリを確保(所有モードになる) - ImageBuffer(const ImageBuffer &other) - : view_(nullptr, other.view_.formatID, 0, other.view_.width, - other.view_.height), - capacity_(0), - allocator_(other.allocator_ - ? other.allocator_ - : &core::memory::DefaultAllocator::instance()), - auxInfo_(other.auxInfo_), origin_(other.origin_), - initPolicy_(InitPolicy::Uninitialized) { - if (other.isValid()) { - allocate(); - copyFrom(other); - } - } - - // コピー代入 - // 参照モードからのコピーでも新しいメモリを確保(所有モードになる) - ImageBuffer &operator=(const ImageBuffer &other) { - if (this != &other) { - deallocate(); - view_.formatID = other.view_.formatID; - view_.width = other.view_.width; - view_.height = other.view_.height; - allocator_ = other.allocator_ - ? other.allocator_ - : &core::memory::DefaultAllocator::instance(); - initPolicy_ = InitPolicy::Uninitialized; - auxInfo_ = other.auxInfo_; - origin_ = other.origin_; - if (other.isValid()) { + // ======================================== + // コンストラクタ / デストラクタ + // ======================================== + + // デフォルトコンストラクタ(空の画像) + ImageBuffer() + : view_(), + capacity_(0), + allocator_(&core::memory::DefaultAllocator::instance()), + auxInfo_(), + origin_(), + initPolicy_(DefaultInitPolicy) + { + } + + // サイズ指定コンストラクタ + // alloc = nullptr の場合、DefaultAllocator を使用 + ImageBuffer(int_fast16_t w, int_fast16_t h, PixelFormatID fmt = PixelFormatIDs::RGBA8_Straight, + InitPolicy init = DefaultInitPolicy, core::memory::IAllocator *alloc = nullptr) + : view_(nullptr, fmt, 0, static_cast(w), static_cast(h)), + capacity_(0), + allocator_(alloc ? alloc : &core::memory::DefaultAllocator::instance()), + auxInfo_(), + origin_(), + initPolicy_(init) + { allocate(); - copyFrom(other); - } - } - return *this; - } - - // ムーブコンストラクタ - ImageBuffer(ImageBuffer &&other) noexcept - : view_(other.view_), capacity_(other.capacity_), - allocator_(other.allocator_), auxInfo_(other.auxInfo_), - origin_(other.origin_), initPolicy_(other.initPolicy_) { - other.view_.data = nullptr; - other.view_.width = other.view_.height = 0; - other.view_.stride = 0; - other.capacity_ = 0; - other.auxInfo_ = PixelAuxInfo(); - other.origin_ = Point(); - } - - // ムーブ代入 - ImageBuffer &operator=(ImageBuffer &&other) noexcept { - if (this != &other) { - deallocate(); - view_ = other.view_; - capacity_ = other.capacity_; - allocator_ = other.allocator_; - initPolicy_ = other.initPolicy_; - auxInfo_ = other.auxInfo_; - origin_ = other.origin_; - - other.view_.data = nullptr; - other.view_.width = other.view_.height = 0; - other.view_.stride = 0; - other.capacity_ = 0; - other.auxInfo_ = PixelAuxInfo(); - other.origin_ = Point(); - } - return *this; - } - - // ======================================== - // リセット - // ======================================== - - /// @brief バッファを解放してクリア(一時オブジェクト生成なし) - /// @note ムーブ代入より軽量。プールでの一括解放に最適。 - void reset() { - deallocate(); - view_.width = 0; - view_.height = 0; - view_.stride = 0; - view_.formatID = nullptr; - allocator_ = nullptr; - auxInfo_ = PixelAuxInfo(); - origin_ = Point(); - } - - // ======================================== - // ビュー取得 - // ======================================== - - // 値で返す(安全性重視、呼び出し側での変更がImageBufferに影響しない) - ViewPort view() { return view_; } - ViewPort view() const { return view_; } - - // 参照で返す(効率重視、直接操作可能) - ViewPort &viewRef() { return view_; } - const ViewPort &viewRef() const { return view_; } - - ViewPort subView(int_fast16_t x, int_fast16_t y, int_fast16_t w, - int_fast16_t h) const { - return view_ops::subView(view_, x, y, w, h); - } - - // サブビューを持つ参照モードImageBufferを作成 - ImageBuffer subBuffer(int_fast16_t x, int_fast16_t y, int_fast16_t w, - int_fast16_t h) const { - return ImageBuffer(view_ops::subView(view_, x, y, w, h)); - } - - // ビューの有効範囲を縮小(メモリ所有権は維持) - // subViewと同じシグネチャ: (x, y, width, height) - void cropView(int_fast16_t x, int_fast16_t y, int_fast16_t w, - int_fast16_t h) { - view_ = view_ops::subView(view_, x, y, w, h); - } - - // ======================================== - // アクセサ(ViewPortに委譲) - // ======================================== - - bool isValid() const { return view_.isValid(); } - - // メモリを所有しているか(false=参照モード、編集禁止) - bool ownsMemory() const { return allocator_ != nullptr; } - - // アロケータを設定(参照モードのバッファに対して、変換時に使用するアロケータを指定) - void setAllocator(core::memory::IAllocator *alloc) { allocator_ = alloc; } - - int16_t width() const { return view_.width; } - int16_t height() const { return view_.height; } - int32_t stride() const { return view_.stride; } - PixelFormatID formatID() const { return view_.formatID; } - - void *data() { return view_.data; } - const void *data() const { return view_.data; } - - void *pixelAt(int x, int y) { return view_.pixelAt(x, y); } - const void *pixelAt(int x, int y) const { return view_.pixelAt(x, y); } - - uint8_t bytesPerPixel() const { return view_.bytesPerPixel(); } - uint32_t totalBytes() const { - // strideが負の場合は絶対値を使用 - int32_t absStride = stride() >= 0 ? stride() : -stride(); - return static_cast(view_.height) * - static_cast(absStride); - } - - // ======================================== - // フォーマット変換 - // ======================================== - - // 右辺値参照版: 同じフォーマットならムーブ、異なるなら変換 - // 使用例: ImageBuffer working = - // std::move(input.buffer).toFormat(PixelFormatIDs::RGBA8_Straight); - // - // mode: - // CopyIfNeeded - 参照モードならコピー作成(デフォルト、編集する場合) - // PreferReference - - // フォーマット一致なら参照のまま返す(読み取り専用の場合) - // - // alloc: - // 新バッファ作成時に使用するアロケータ(オプション) - // - nullptr(デフォルト): 自身のallocator_を使用 - // - non-null: 指定されたアロケータを使用 - // 注: 参照モードバッファにsetAllocator()を呼ぶと、デストラクタが - // 非所有メモリを解放しようとするバグがあるため、このパラメータで - // 新バッファのアロケータを安全に指定できる - ImageBuffer toFormat(PixelFormatID target, - FormatConversion mode = FormatConversion::CopyIfNeeded, - core::memory::IAllocator *alloc = nullptr, - const FormatConverter *converter = nullptr) && { - // 新バッファ用アロケータを決定 - core::memory::IAllocator *newAlloc = alloc ? alloc : allocator_; - - if (view_.formatID == target) { - // フォーマット一致 - if (mode == FormatConversion::PreferReference) { - // 参照希望: そのまま返す(参照モードでも所有モードでも) - return std::move(*this); - } - if (ownsMemory()) { - // 所有モード: そのまま返す - return std::move(*this); - } - // 参照モード + CopyIfNeeded: コピー作成 - ImageBuffer copied(view_.width, view_.height, view_.formatID, - InitPolicy::Uninitialized, newAlloc); - if (isValid() && copied.isValid()) { - view_ops::copy(copied.view_, 0, 0, view_, 0, 0, view_.width, - view_.height); - } - return copied; - } - // フォーマット不一致: 常に変換(新バッファ作成) - ImageBuffer converted(view_.width, view_.height, target, - InitPolicy::Uninitialized, newAlloc); - if (isValid() && converted.isValid()) { - // 変換パスを事前解決し、行単位で変換(ストライドを正しく処理) - // 外部からコンバータが渡されていればそれを使用、なければ自前で解決 - FormatConverter resolved; - if (converter) { - resolved = *converter; - } else { - bool hasAuxData = (auxInfo_.palette != nullptr) || - (auxInfo_.colorKeyRGBA8 != auxInfo_.colorKeyReplace); - const PixelAuxInfo *auxPtr = hasAuxData ? &auxInfo_ : nullptr; - resolved = resolveConverter(view_.formatID, target, auxPtr); - } - if (resolved) { - for (int_fast16_t y = 0; y < view_.height; ++y) { - // ViewPortのx,yオフセットを考慮 - const uint8_t *srcRow = static_cast(view_.data) + - (view_.y + y) * view_.stride + - view_.x * view_.bytesPerPixel(); - uint8_t *dstRow = static_cast(converted.view_.data) + - y * converted.view_.stride; - resolved(dstRow, srcRow, view_.width); + } + + // 外部ViewPortを参照(メモリ所有しない) + // 使用例: ImageBuffer ref(someViewPort); + explicit ImageBuffer(ViewPort view) + : view_(view), + capacity_(0), + allocator_(nullptr) // nullなのでデストラクタで解放しない + , + auxInfo_(), + origin_(), + initPolicy_(InitPolicy::Zero) + { + } + + // デストラクタ + ~ImageBuffer() + { + deallocate(); + } + + // ======================================== + // コピー / ムーブセマンティクス + // ======================================== + + // コピーコンストラクタ(ディープコピー) + // 参照モードからのコピーでも新しいメモリを確保(所有モードになる) + ImageBuffer(const ImageBuffer &other) + : view_(nullptr, other.view_.formatID, 0, other.view_.width, other.view_.height), + capacity_(0), + allocator_(other.allocator_ ? other.allocator_ : &core::memory::DefaultAllocator::instance()), + auxInfo_(other.auxInfo_), + origin_(other.origin_), + initPolicy_(InitPolicy::Uninitialized) + { + if (other.isValid()) { + allocate(); + copyFrom(other); } - } } - return converted; - } - // ======================================== - // 補助情報(パレット、カラーキー等) - // ======================================== + // コピー代入 + // 参照モードからのコピーでも新しいメモリを確保(所有モードになる) + ImageBuffer &operator=(const ImageBuffer &other) + { + if (this != &other) { + deallocate(); + view_.formatID = other.view_.formatID; + view_.width = other.view_.width; + view_.height = other.view_.height; + allocator_ = other.allocator_ ? other.allocator_ : &core::memory::DefaultAllocator::instance(); + initPolicy_ = InitPolicy::Uninitialized; + auxInfo_ = other.auxInfo_; + origin_ = other.origin_; + if (other.isValid()) { + allocate(); + copyFrom(other); + } + } + return *this; + } - const PixelAuxInfo &auxInfo() const { return auxInfo_; } - PixelAuxInfo &auxInfo() { return auxInfo_; } + // ムーブコンストラクタ + ImageBuffer(ImageBuffer &&other) noexcept + : view_(other.view_), + capacity_(other.capacity_), + allocator_(other.allocator_), + auxInfo_(other.auxInfo_), + origin_(other.origin_), + initPolicy_(other.initPolicy_) + { + other.view_.data = nullptr; + other.view_.width = other.view_.height = 0; + other.view_.stride = 0; + other.capacity_ = 0; + other.auxInfo_ = PixelAuxInfo(); + other.origin_ = Point(); + } - // パレット設定(PaletteData 経由) - void setPalette(const PaletteData &pal) { - auxInfo_.palette = pal.data; - auxInfo_.paletteFormat = pal.format; - auxInfo_.paletteColorCount = pal.colorCount; - } + // ムーブ代入 + ImageBuffer &operator=(ImageBuffer &&other) noexcept + { + if (this != &other) { + deallocate(); + view_ = other.view_; + capacity_ = other.capacity_; + allocator_ = other.allocator_; + initPolicy_ = other.initPolicy_; + auxInfo_ = other.auxInfo_; + origin_ = other.origin_; + + other.view_.data = nullptr; + other.view_.width = other.view_.height = 0; + other.view_.stride = 0; + other.capacity_ = 0; + other.auxInfo_ = PixelAuxInfo(); + other.origin_ = Point(); + } + return *this; + } - // パレット設定(個別引数) - void setPalette(const void *data, PixelFormatID fmt, uint16_t count) { - auxInfo_.palette = data; - auxInfo_.paletteFormat = fmt; - auxInfo_.paletteColorCount = count; - } + // ======================================== + // リセット + // ======================================== + + /// @brief バッファを解放してクリア(一時オブジェクト生成なし) + /// @note ムーブ代入より軽量。プールでの一括解放に最適。 + void reset() + { + deallocate(); + view_.width = 0; + view_.height = 0; + view_.stride = 0; + view_.formatID = nullptr; + allocator_ = nullptr; + auxInfo_ = PixelAuxInfo(); + origin_ = Point(); + } - // ======================================== - // Origin(Q16.16ワールド座標) - // ======================================== + // ======================================== + // ビュー取得 + // ======================================== - /// @brief originを取得(Q16.16精度) - Point origin() const { return origin_; } + // 値で返す(安全性重視、呼び出し側での変更がImageBufferに影響しない) + ViewPort view() + { + return view_; + } + ViewPort view() const + { + return view_; + } - /// @brief originを設定(Q16.16精度) - void setOrigin(Point p) { origin_ = p; } + // 参照で返す(効率重視、直接操作可能) + ViewPort &viewRef() + { + return view_; + } + const ViewPort &viewRef() const + { + return view_; + } - /// @brief originのX座標を取得(Q16.16精度) - int_fixed originX() const { return origin_.x; } + ViewPort subView(int_fast16_t x, int_fast16_t y, int_fast16_t w, int_fast16_t h) const + { + return view_ops::subView(view_, x, y, w, h); + } - /// @brief originのY座標を取得(Q16.16精度) - int_fixed originY() const { return origin_.y; } + // サブビューを持つ参照モードImageBufferを作成 + ImageBuffer subBuffer(int_fast16_t x, int_fast16_t y, int_fast16_t w, int_fast16_t h) const + { + return ImageBuffer(view_ops::subView(view_, x, y, w, h)); + } - // ======================================== - // X座標オフセット(整数精度ヘルパー) - // ======================================== + // ビューの有効範囲を縮小(メモリ所有権は維持) + // subViewと同じシグネチャ: (x, y, width, height) + void cropView(int_fast16_t x, int_fast16_t y, int_fast16_t w, int_fast16_t h) + { + view_ = view_ops::subView(view_, x, y, w, h); + } - /// @brief X座標オフセットを取得(originの整数部) - int16_t startX() const { return static_cast(from_fixed(origin_.x)); } + // ======================================== + // アクセサ(ViewPortに委譲) + // ======================================== - /// @brief X終端座標を取得(startX + width) - int16_t endX() const { return static_cast(startX() + width()); } + bool isValid() const + { + return view_.isValid(); + } - /// @brief X座標オフセットを設定(整数精度) - void setStartX(int16_t x) { origin_.x = to_fixed(x); } + // メモリを所有しているか(false=参照モード、編集禁止) + bool ownsMemory() const + { + return allocator_ != nullptr; + } - /// @brief X座標オフセットを加算(整数精度) - void addOffset(int16_t offset) { origin_.x += to_fixed(offset); } + // アロケータを設定(参照モードのバッファに対して、変換時に使用するアロケータを指定) + void setAllocator(core::memory::IAllocator *alloc) + { + allocator_ = alloc; + } - /// @brief ソースバッファのデータを自身にunder合成 - /// @param src ソースバッファ(ワールド座標origin設定済み) - /// @return 成功時true - bool blendFrom(const ImageBuffer &src); + int16_t width() const + { + return view_.width; + } + int16_t height() const + { + return view_.height; + } + int32_t stride() const + { + return view_.stride; + } + PixelFormatID formatID() const + { + return view_.formatID; + } -private: - ViewPort view_; // コンポジション: 画像データへのビュー - size_t capacity_; - core::memory::IAllocator *allocator_; - PixelAuxInfo auxInfo_; // 補助情報(パレット、カラーキー等) - Point origin_; // バッファ原点(Q16.16ワールド座標) - InitPolicy initPolicy_; - - void allocate() { - // view_.formatIDがnullptrでないことを確認 - FLEXIMG_ASSERT(view_.formatID != nullptr, "PixelFormatID is null"); - - // bit-packed形式に対応したstride計算 - if (view_.formatID->pixelsPerUnit > 1) { - // bit-packed形式: 必要なユニット数 × bytesPerUnit - int units = (view_.width + view_.formatID->pixelsPerUnit - 1) / - view_.formatID->pixelsPerUnit; - view_.stride = static_cast(units * view_.formatID->bytesPerUnit); - } else { - // 通常形式: width × bytesPerPixel - auto bytesPerPixel = view_.formatID->bytesPerPixel; - view_.stride = static_cast(view_.width * bytesPerPixel); - } - capacity_ = - static_cast(view_.stride) * static_cast(view_.height); - if (capacity_ > 0 && allocator_) { - view_.data = allocator_->allocate(capacity_); - FLEXIMG_REQUIRE(view_.data != nullptr, "Memory allocation failed"); - if (view_.data) { - switch (initPolicy_) { - case InitPolicy::Zero: - std::memset(view_.data, 0, capacity_); - break; - case InitPolicy::DebugPattern: { - // 確保ごとに異なる値でmemset(未初期化使用のバグ検出用) - static uint8_t counter = 0xCD; - std::memset(view_.data, counter++, capacity_); - break; + void *data() + { + return view_.data; + } + const void *data() const + { + return view_.data; + } + + void *pixelAt(int x, int y) + { + return view_.pixelAt(x, y); + } + const void *pixelAt(int x, int y) const + { + return view_.pixelAt(x, y); + } + + uint8_t bytesPerPixel() const + { + return view_.bytesPerPixel(); + } + uint32_t totalBytes() const + { + // strideが負の場合は絶対値を使用 + int32_t absStride = stride() >= 0 ? stride() : -stride(); + return static_cast(view_.height) * static_cast(absStride); + } + + // ======================================== + // フォーマット変換 + // ======================================== + + // 右辺値参照版: 同じフォーマットならムーブ、異なるなら変換 + // 使用例: ImageBuffer working = + // std::move(input.buffer).toFormat(PixelFormatIDs::RGBA8_Straight); + // + // mode: + // CopyIfNeeded - 参照モードならコピー作成(デフォルト、編集する場合) + // PreferReference - + // フォーマット一致なら参照のまま返す(読み取り専用の場合) + // + // alloc: + // 新バッファ作成時に使用するアロケータ(オプション) + // - nullptr(デフォルト): 自身のallocator_を使用 + // - non-null: 指定されたアロケータを使用 + // 注: 参照モードバッファにsetAllocator()を呼ぶと、デストラクタが + // 非所有メモリを解放しようとするバグがあるため、このパラメータで + // 新バッファのアロケータを安全に指定できる + ImageBuffer toFormat(PixelFormatID target, FormatConversion mode = FormatConversion::CopyIfNeeded, + core::memory::IAllocator *alloc = nullptr, const FormatConverter *converter = nullptr) && + { + // 新バッファ用アロケータを決定 + core::memory::IAllocator *newAlloc = alloc ? alloc : allocator_; + + if (view_.formatID == target) { + // フォーマット一致 + if (mode == FormatConversion::PreferReference) { + // 参照希望: そのまま返す(参照モードでも所有モードでも) + return std::move(*this); + } + if (ownsMemory()) { + // 所有モード: そのまま返す + return std::move(*this); + } + // 参照モード + CopyIfNeeded: コピー作成 + ImageBuffer copied(view_.width, view_.height, view_.formatID, InitPolicy::Uninitialized, newAlloc); + if (isValid() && copied.isValid()) { + view_ops::copy(copied.view_, 0, 0, view_, 0, 0, view_.width, view_.height); + } + return copied; } - case InitPolicy::Uninitialized: - // 初期化スキップ - break; + // フォーマット不一致: 常に変換(新バッファ作成) + ImageBuffer converted(view_.width, view_.height, target, InitPolicy::Uninitialized, newAlloc); + if (isValid() && converted.isValid()) { + // 変換パスを事前解決し、行単位で変換(ストライドを正しく処理) + // 外部からコンバータが渡されていればそれを使用、なければ自前で解決 + FormatConverter resolved; + if (converter) { + resolved = *converter; + } else { + bool hasAuxData = (auxInfo_.palette != nullptr) || (auxInfo_.colorKeyRGBA8 != auxInfo_.colorKeyReplace); + const PixelAuxInfo *auxPtr = hasAuxData ? &auxInfo_ : nullptr; + resolved = resolveConverter(view_.formatID, target, auxPtr); + } + if (resolved) { + for (int_fast16_t y = 0; y < view_.height; ++y) { + // ViewPortのx,yオフセットを考慮 + const uint8_t *srcRow = static_cast(view_.data) + (view_.y + y) * view_.stride + + view_.x * view_.bytesPerPixel(); + uint8_t *dstRow = static_cast(converted.view_.data) + y * converted.view_.stride; + resolved(dstRow, srcRow, view_.width); + } + } } + return converted; + } + + // ======================================== + // 補助情報(パレット、カラーキー等) + // ======================================== + + const PixelAuxInfo &auxInfo() const + { + return auxInfo_; + } + PixelAuxInfo &auxInfo() + { + return auxInfo_; + } + + // パレット設定(PaletteData 経由) + void setPalette(const PaletteData &pal) + { + auxInfo_.palette = pal.data; + auxInfo_.paletteFormat = pal.format; + auxInfo_.paletteColorCount = pal.colorCount; + } + + // パレット設定(個別引数) + void setPalette(const void *data, PixelFormatID fmt, uint16_t count) + { + auxInfo_.palette = data; + auxInfo_.paletteFormat = fmt; + auxInfo_.paletteColorCount = count; + } + + // ======================================== + // Origin(Q16.16ワールド座標) + // ======================================== + + /// @brief originを取得(Q16.16精度) + Point origin() const + { + return origin_; + } + + /// @brief originを設定(Q16.16精度) + void setOrigin(Point p) + { + origin_ = p; + } + + /// @brief originのX座標を取得(Q16.16精度) + int_fixed originX() const + { + return origin_.x; + } + + /// @brief originのY座標を取得(Q16.16精度) + int_fixed originY() const + { + return origin_.y; + } + + // ======================================== + // X座標オフセット(整数精度ヘルパー) + // ======================================== + + /// @brief X座標オフセットを取得(originの整数部) + int16_t startX() const + { + return static_cast(from_fixed(origin_.x)); + } + + /// @brief X終端座標を取得(startX + width) + int16_t endX() const + { + return static_cast(startX() + width()); + } + + /// @brief X座標オフセットを設定(整数精度) + void setStartX(int16_t x) + { + origin_.x = to_fixed(x); + } + + /// @brief X座標オフセットを加算(整数精度) + void addOffset(int16_t offset) + { + origin_.x += to_fixed(offset); + } + + /// @brief ソースバッファのデータを自身にunder合成 + /// @param src ソースバッファ(ワールド座標origin設定済み) + /// @return 成功時true + bool blendFrom(const ImageBuffer &src); + +private: + ViewPort view_; // コンポジション: 画像データへのビュー + size_t capacity_; + core::memory::IAllocator *allocator_; + PixelAuxInfo auxInfo_; // 補助情報(パレット、カラーキー等) + Point origin_; // バッファ原点(Q16.16ワールド座標) + InitPolicy initPolicy_; + + void allocate() + { + // view_.formatIDがnullptrでないことを確認 + FLEXIMG_ASSERT(view_.formatID != nullptr, "PixelFormatID is null"); + + // bit-packed形式に対応したstride計算 + if (view_.formatID->pixelsPerUnit > 1) { + // bit-packed形式: 必要なユニット数 × bytesPerUnit + int units = (view_.width + view_.formatID->pixelsPerUnit - 1) / view_.formatID->pixelsPerUnit; + view_.stride = static_cast(units * view_.formatID->bytesPerUnit); + } else { + // 通常形式: width × bytesPerPixel + auto bytesPerPixel = view_.formatID->bytesPerPixel; + view_.stride = static_cast(view_.width * bytesPerPixel); + } + capacity_ = static_cast(view_.stride) * static_cast(view_.height); + if (capacity_ > 0 && allocator_) { + view_.data = allocator_->allocate(capacity_); + FLEXIMG_REQUIRE(view_.data != nullptr, "Memory allocation failed"); + if (view_.data) { + switch (initPolicy_) { + case InitPolicy::Zero: + std::memset(view_.data, 0, capacity_); + break; + case InitPolicy::DebugPattern: { + // 確保ごとに異なる値でmemset(未初期化使用のバグ検出用) + static uint8_t counter = 0xCD; + std::memset(view_.data, counter++, capacity_); + break; + } + case InitPolicy::Uninitialized: + // 初期化スキップ + break; + } #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().recordAlloc(capacity_, view_.width, - view_.height); + PerfMetrics::instance().recordAlloc(capacity_, view_.width, view_.height); #endif - } + } + } } - } - void deallocate() { - if (view_.data && allocator_) { + void deallocate() + { + if (view_.data && allocator_) { #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().recordFree(capacity_); + PerfMetrics::instance().recordFree(capacity_); #endif - allocator_->deallocate(view_.data); - } - view_.data = nullptr; - capacity_ = 0; - } - - void copyFrom(const ImageBuffer &other) { - if (!isValid() || !other.isValid()) - return; - int32_t copyBytes = std::min(view_.stride, other.view_.stride); - int16_t copyHeight = std::min(view_.height, other.view_.height); - for (int_fast16_t y = 0; y < copyHeight; ++y) { - // ViewPortのx,yオフセットを考慮 - std::memcpy(static_cast(view_.data) + - (view_.y + y) * view_.stride + - view_.x * view_.bytesPerPixel(), - static_cast(other.view_.data) + - (other.view_.y + y) * other.view_.stride + - other.view_.x * other.view_.bytesPerPixel(), - static_cast(copyBytes)); - } - } + allocator_->deallocate(view_.data); + } + view_.data = nullptr; + capacity_ = 0; + } + + void copyFrom(const ImageBuffer &other) + { + if (!isValid() || !other.isValid()) return; + int32_t copyBytes = std::min(view_.stride, other.view_.stride); + int16_t copyHeight = std::min(view_.height, other.view_.height); + for (int_fast16_t y = 0; y < copyHeight; ++y) { + // ViewPortのx,yオフセットを考慮 + std::memcpy( + static_cast(view_.data) + (view_.y + y) * view_.stride + view_.x * view_.bytesPerPixel(), + static_cast(other.view_.data) + (other.view_.y + y) * other.view_.stride + + other.view_.x * other.view_.bytesPerPixel(), + static_cast(copyBytes)); + } + } }; // ======================================================================== // ImageBuffer::blendFrom() 実装 // ======================================================================== -inline bool ImageBuffer::blendFrom(const ImageBuffer &src) { - if (!isValid() || !src.isValid() || !view_.data) - return false; - - const auto &srcView = src.viewRef(); - const int_fast16_t dstStartX = startX(); - const int_fast16_t srcStartX = src.startX(); - - // クリッピング: srcのうちdstバッファ範囲内にある部分 - const int_fast16_t clippedStart = std::max(srcStartX, dstStartX); - const int_fast16_t clippedEnd = std::min(src.endX(), endX()); - int_fast16_t remaining = static_cast(clippedEnd - clippedStart); - if (remaining <= 0) - return true; // 範囲外、何もしない - - const size_t dstPixelBytes = - static_cast(view_.formatID->bytesPerPixel); - const uint8_t srcPixelBits = srcView.formatID->bitsPerPixel; - - // ViewPortのy成分を含む行ベースアドレス - // x成分はビット単位で一括計算し、>>3でバイト、&7でビット端数を取り出す - uint8_t *dstRow = static_cast(view_.data) + - view_.y * view_.stride + - static_cast(view_.x) * dstPixelBytes; - const uint8_t *srcRowBase = - static_cast(srcView.data) + srcView.y * srcView.stride; - - PixelFormatID srcFmt = srcView.formatID; - const PixelAuxInfo *srcAux = &src.auxInfo(); - - // 共通: クリッピング開始位置のポインタ計算 - const int_fast32_t srcTotalBits = - (srcView.x + clippedStart - srcStartX) * srcPixelBits; - const void *srcPtr = srcRowBase + static_cast(srcTotalBits >> 3); - void *dstPtr = - dstRow + static_cast(clippedStart - dstStartX) * dstPixelBytes; - - auto blendFunc = srcFmt->blendUnderStraight; - if (blendFunc) { - // 直接ブレンド(RGBA8_Straight等、blendUnderStraight実装済みフォーマット) - blendFunc(dstPtr, srcPtr, remaining, srcAux); - } else { - // フォールバック: チャンク単位でRGBA8_Straightに変換してからブレンド - auto converter = - resolveConverter(srcFmt, PixelFormatIDs::RGBA8_Straight, srcAux); - if (!converter) - return false; - converter.ctx.pixelOffsetInByte = - static_cast((srcTotalBits & 7) >> (srcPixelBits >> 1)); - auto straightBlend = PixelFormatIDs::RGBA8_Straight->blendUnderStraight; - constexpr int_fast16_t CHUNK_SIZE = 64; - uint8_t tempBuf[CHUNK_SIZE * 4]; - int_fast16_t cursor = clippedStart; - - // ループ外でsrcPtr進行量とビット端数を事前計算 - // (CHUNK_SIZE=64はpixelsPerByte(8,4,2)の倍数のためビット端数はチャンク間で不変) - const uint8_t *srcChunkPtr = static_cast(srcPtr); - const size_t srcBytesPerChunk = - static_cast(CHUNK_SIZE * srcPixelBits >> 3); - - do { - int_fast16_t chunk = std::min(remaining, CHUNK_SIZE); - converter(tempBuf, srcChunkPtr, chunk); - void *dstChunkPtr = - dstRow + static_cast(cursor - dstStartX) * dstPixelBytes; - straightBlend(dstChunkPtr, tempBuf, chunk, nullptr); - srcChunkPtr += srcBytesPerChunk; - cursor += chunk; - remaining -= chunk; - } while (remaining > 0); - } - return true; +inline bool ImageBuffer::blendFrom(const ImageBuffer &src) +{ + if (!isValid() || !src.isValid() || !view_.data) return false; + + const auto &srcView = src.viewRef(); + const int_fast16_t dstStartX = startX(); + const int_fast16_t srcStartX = src.startX(); + + // クリッピング: srcのうちdstバッファ範囲内にある部分 + const int_fast16_t clippedStart = std::max(srcStartX, dstStartX); + const int_fast16_t clippedEnd = std::min(src.endX(), endX()); + int_fast16_t remaining = static_cast(clippedEnd - clippedStart); + if (remaining <= 0) return true; // 範囲外、何もしない + + const size_t dstPixelBytes = static_cast(view_.formatID->bytesPerPixel); + const uint8_t srcPixelBits = srcView.formatID->bitsPerPixel; + + // ViewPortのy成分を含む行ベースアドレス + // x成分はビット単位で一括計算し、>>3でバイト、&7でビット端数を取り出す + uint8_t *dstRow = + static_cast(view_.data) + view_.y * view_.stride + static_cast(view_.x) * dstPixelBytes; + const uint8_t *srcRowBase = static_cast(srcView.data) + srcView.y * srcView.stride; + + PixelFormatID srcFmt = srcView.formatID; + const PixelAuxInfo *srcAux = &src.auxInfo(); + + // 共通: クリッピング開始位置のポインタ計算 + const int_fast32_t srcTotalBits = (srcView.x + clippedStart - srcStartX) * srcPixelBits; + const void *srcPtr = srcRowBase + static_cast(srcTotalBits >> 3); + void *dstPtr = dstRow + static_cast(clippedStart - dstStartX) * dstPixelBytes; + + auto blendFunc = srcFmt->blendUnderStraight; + if (blendFunc) { + // 直接ブレンド(RGBA8_Straight等、blendUnderStraight実装済みフォーマット) + blendFunc(dstPtr, srcPtr, remaining, srcAux); + } else { + // フォールバック: チャンク単位でRGBA8_Straightに変換してからブレンド + auto converter = resolveConverter(srcFmt, PixelFormatIDs::RGBA8_Straight, srcAux); + if (!converter) return false; + converter.ctx.pixelOffsetInByte = static_cast((srcTotalBits & 7) >> (srcPixelBits >> 1)); + auto straightBlend = PixelFormatIDs::RGBA8_Straight->blendUnderStraight; + constexpr int_fast16_t CHUNK_SIZE = 64; + uint8_t tempBuf[CHUNK_SIZE * 4]; + int_fast16_t cursor = clippedStart; + + // ループ外でsrcPtr進行量とビット端数を事前計算 + // (CHUNK_SIZE=64はpixelsPerByte(8,4,2)の倍数のためビット端数はチャンク間で不変) + const uint8_t *srcChunkPtr = static_cast(srcPtr); + const size_t srcBytesPerChunk = static_cast(CHUNK_SIZE * srcPixelBits >> 3); + + do { + int_fast16_t chunk = std::min(remaining, CHUNK_SIZE); + converter(tempBuf, srcChunkPtr, chunk); + void *dstChunkPtr = dstRow + static_cast(cursor - dstStartX) * dstPixelBytes; + straightBlend(dstChunkPtr, tempBuf, chunk, nullptr); + srcChunkPtr += srcBytesPerChunk; + cursor += chunk; + remaining -= chunk; + } while (remaining > 0); + } + return true; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMAGE_BUFFER_H +#endif // FLEXIMG_IMAGE_BUFFER_H diff --git a/src/fleximg/image/image_buffer_entry_pool.h b/src/fleximg/image/image_buffer_entry_pool.h index 7167aff..3f3e301 100644 --- a/src/fleximg/image/image_buffer_entry_pool.h +++ b/src/fleximg/image/image_buffer_entry_pool.h @@ -40,123 +40,131 @@ namespace FLEXIMG_NAMESPACE { class ImageBufferEntryPool { public: - /// @brief プールサイズ(組み込み向け固定上限) - static constexpr int POOL_SIZE_BITS = 3; // 2^3 = 8エントリ - static constexpr int POOL_SIZE = 1 << POOL_SIZE_BITS; - - /// @brief エントリ構造体 - /// @note 座標情報はImageBuffer.startX()で管理 - struct Entry { - ImageBuffer buffer; ///< バッファ本体(startX内包) - bool inUse = false; ///< 使用中フラグ - }; - - // ======================================== - // 構築・破棄 - // ======================================== - - /// @brief デフォルトコンストラクタ - ImageBufferEntryPool() : nextHint_(0) { - // エントリを初期化 - for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { - entries_[i].inUse = false; + /// @brief プールサイズ(組み込み向け固定上限) + static constexpr int POOL_SIZE_BITS = 3; // 2^3 = 8エントリ + static constexpr int POOL_SIZE = 1 << POOL_SIZE_BITS; + + /// @brief エントリ構造体 + /// @note 座標情報はImageBuffer.startX()で管理 + struct Entry { + ImageBuffer buffer; ///< バッファ本体(startX内包) + bool inUse = false; ///< 使用中フラグ + }; + + // ======================================== + // 構築・破棄 + // ======================================== + + /// @brief デフォルトコンストラクタ + ImageBufferEntryPool() : nextHint_(0) + { + // エントリを初期化 + for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { + entries_[i].inUse = false; + } } - } - - /// @brief デストラクタ - ~ImageBufferEntryPool() { releaseAll(); } - - // コピー禁止 - ImageBufferEntryPool(const ImageBufferEntryPool &) = delete; - ImageBufferEntryPool &operator=(const ImageBufferEntryPool &) = delete; - - // ムーブ禁止(アドレス固定が必要) - ImageBufferEntryPool(ImageBufferEntryPool &&) = delete; - ImageBufferEntryPool &operator=(ImageBufferEntryPool &&) = delete; - - // ======================================== - // エントリ管理 - // ======================================== - - /// @brief 空きエントリを取得 - /// @return 取得したエントリへのポインタ。空きがない場合はnullptr - /// @note バッファ/範囲のリセットは行わない(呼び出し側で初期化される) - /// @note ヒント付き循環探索でO(1)に近い性能を実現 - Entry *acquire() { - // nextHint_から開始して循環探索 - uint_fast8_t idx = nextHint_; - for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { - idx = (idx + 1) & (POOL_SIZE - 1); - if (!entries_[idx].inUse) { - entries_[idx].inUse = true; - nextHint_ = idx; - return &entries_[idx]; - } + + /// @brief デストラクタ + ~ImageBufferEntryPool() + { + releaseAll(); } - return nullptr; // 枯渇 - } - - /// @brief エントリを返却 - /// @param entry 返却するエントリ - /// @note バッファも解放(再取得時のムーブ代入での二重解放を防止) - void release(Entry *entry) { - size_t idx = static_cast(entry - entries_); - if (idx < POOL_SIZE) { - if (!entry->inUse) { - FLEXIMG_DEBUG_WARN("DOUBLE RELEASE: entry=%p idx=%d", - static_cast(entry), static_cast(idx)); - } - if (entry->inUse) { - entry->buffer.reset(); // バッファ解放(重要: 再取得前にクリア) - entry->inUse = false; - } + + // コピー禁止 + ImageBufferEntryPool(const ImageBufferEntryPool &) = delete; + ImageBufferEntryPool &operator=(const ImageBufferEntryPool &) = delete; + + // ムーブ禁止(アドレス固定が必要) + ImageBufferEntryPool(ImageBufferEntryPool &&) = delete; + ImageBufferEntryPool &operator=(ImageBufferEntryPool &&) = delete; + + // ======================================== + // エントリ管理 + // ======================================== + + /// @brief 空きエントリを取得 + /// @return 取得したエントリへのポインタ。空きがない場合はnullptr + /// @note バッファ/範囲のリセットは行わない(呼び出し側で初期化される) + /// @note ヒント付き循環探索でO(1)に近い性能を実現 + Entry *acquire() + { + // nextHint_から開始して循環探索 + uint_fast8_t idx = nextHint_; + for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { + idx = (idx + 1) & (POOL_SIZE - 1); + if (!entries_[idx].inUse) { + entries_[idx].inUse = true; + nextHint_ = idx; + return &entries_[idx]; + } + } + return nullptr; // 枯渇 } - } - - /// @brief 全エントリを一括解放(フレーム終了時) - void releaseAll() { - for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { - if (entries_[i].inUse) { - entries_[i].buffer.reset(); // 軽量リセット - entries_[i].inUse = false; - } + + /// @brief エントリを返却 + /// @param entry 返却するエントリ + /// @note バッファも解放(再取得時のムーブ代入での二重解放を防止) + void release(Entry *entry) + { + size_t idx = static_cast(entry - entries_); + if (idx < POOL_SIZE) { + if (!entry->inUse) { + FLEXIMG_DEBUG_WARN("DOUBLE RELEASE: entry=%p idx=%d", static_cast(entry), + static_cast(idx)); + } + if (entry->inUse) { + entry->buffer.reset(); // バッファ解放(重要: 再取得前にクリア) + entry->inUse = false; + } + } } - nextHint_ = 0; // ヒントをリセット - } - - // ======================================== - // 状態照会 - // ======================================== - - /// @brief 使用中のエントリ数を取得 - uint_fast8_t usedCount() const { - uint_fast8_t count = 0; - for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { - if (entries_[i].inUse) - ++count; + + /// @brief 全エントリを一括解放(フレーム終了時) + void releaseAll() + { + for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { + if (entries_[i].inUse) { + entries_[i].buffer.reset(); // 軽量リセット + entries_[i].inUse = false; + } + } + nextHint_ = 0; // ヒントをリセット } - return count; - } - - /// @brief 空きエントリ数を取得 - uint_fast8_t freeCount() const { - return static_cast(POOL_SIZE - usedCount()); - } - - /// @brief 空きがあるか - bool hasAvailable() const { - for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { - if (!entries_[i].inUse) - return true; + + // ======================================== + // 状態照会 + // ======================================== + + /// @brief 使用中のエントリ数を取得 + uint_fast8_t usedCount() const + { + uint_fast8_t count = 0; + for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { + if (entries_[i].inUse) ++count; + } + return count; + } + + /// @brief 空きエントリ数を取得 + uint_fast8_t freeCount() const + { + return static_cast(POOL_SIZE - usedCount()); + } + + /// @brief 空きがあるか + bool hasAvailable() const + { + for (uint_fast8_t i = 0; i < POOL_SIZE; ++i) { + if (!entries_[i].inUse) return true; + } + return false; } - return false; - } private: - Entry entries_[POOL_SIZE]; ///< エントリ配列 - uint8_t nextHint_; ///< 次回探索開始位置(循環探索用) + Entry entries_[POOL_SIZE]; ///< エントリ配列 + uint8_t nextHint_; ///< 次回探索開始位置(循環探索用) }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMAGE_BUFFER_ENTRY_POOL_H +#endif // FLEXIMG_IMAGE_BUFFER_ENTRY_POOL_H diff --git a/src/fleximg/image/pixel_format.h b/src/fleximg/image/pixel_format.h index d1ac931..7f70735 100644 --- a/src/fleximg/image/pixel_format.h +++ b/src/fleximg/image/pixel_format.h @@ -23,12 +23,12 @@ namespace FLEXIMG_NAMESPACE { // - 境界ピクセルはクランプ(端のピクセルを複製) // enum EdgeFadeFlags : uint8_t { - EdgeFade_None = 0, - EdgeFade_Left = 0x01, - EdgeFade_Right = 0x02, - EdgeFade_Top = 0x04, - EdgeFade_Bottom = 0x08, - EdgeFade_All = 0x0F + EdgeFade_None = 0, + EdgeFade_Left = 0x01, + EdgeFade_Right = 0x02, + EdgeFade_Top = 0x04, + EdgeFade_Bottom = 0x08, + EdgeFade_All = 0x0F }; // ======================================================================== @@ -37,8 +37,8 @@ enum EdgeFadeFlags : uint8_t { // バイリニア補間の重み(チャンク用、fx/fyのみ) struct BilinearWeightXY { - uint8_t fx; // X方向小数部(0-255) - uint8_t fy; // Y方向小数部(0-255) + uint8_t fx; // X方向小数部(0-255) + uint8_t fy; // Y方向小数部(0-255) }; // edgeFlags の説明(チャンク用の別配列として管理、copyQuadDDA 内で生成) @@ -58,18 +58,17 @@ struct BilinearWeightXY { // struct DDAParam { - int32_t srcStride; // ソースのストライド(バイト数) - int32_t srcWidth; // ソース幅(copyQuadDDA用、境界クランプ) - int32_t srcHeight; // ソース高さ(copyQuadDDA用、境界クランプ) - int_fixed srcX; // ソース開始X座標(Q16.16固定小数点) - int_fixed srcY; // ソース開始Y座標(Q16.16固定小数点) - int_fixed incrX; // 1ピクセルあたりのX増分(Q16.16固定小数点) - int_fixed incrY; // 1ピクセルあたりのY増分(Q16.16固定小数点) - - // バイリニア補間用 - BilinearWeightXY *weightsXY; // 重み出力先(チャンク用) - uint8_t - *edgeFlags; // 境界フラグ出力先(チャンク用、copyQuadDDA内で生成、必須) + int32_t srcStride; // ソースのストライド(バイト数) + int32_t srcWidth; // ソース幅(copyQuadDDA用、境界クランプ) + int32_t srcHeight; // ソース高さ(copyQuadDDA用、境界クランプ) + int_fixed srcX; // ソース開始X座標(Q16.16固定小数点) + int_fixed srcY; // ソース開始Y座標(Q16.16固定小数点) + int_fixed incrX; // 1ピクセルあたりのX増分(Q16.16固定小数点) + int_fixed incrY; // 1ピクセルあたりのY増分(Q16.16固定小数点) + + // バイリニア補間用 + BilinearWeightXY *weightsXY; // 重み出力先(チャンク用) + uint8_t *edgeFlags; // 境界フラグ出力先(チャンク用、copyQuadDDA内で生成、必須) }; // DDA行転写関数の型定義 @@ -77,16 +76,14 @@ struct DDAParam { // srcData: ソースデータ先頭 // count: 転写ピクセル数 // param: DDAパラメータ(const、関数内でローカルコピーして使用) -using CopyRowDDA_Func = void (*)(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param); +using CopyRowDDA_Func = void (*)(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); // DDA 4ピクセル抽出関数の型定義(バイリニア補間用) // dst: 出力先バッファ([p00,p10,p01,p11] × count) // srcData: ソースデータ先頭 // count: 抽出ピクセル数 // param: DDAパラメータ(srcWidth/srcHeight/weightsを使用) -using CopyQuadDDA_Func = void (*)(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param); +using CopyQuadDDA_Func = void (*)(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); // 前方宣言 struct PixelFormatDescriptor; @@ -102,30 +99,33 @@ using PixelFormatID = const PixelFormatDescriptor *; // ======================================================================== struct PixelAuxInfo { - // パレット情報(インデックスフォーマット用) - const void *palette = nullptr; // パレットデータポインタ(非所有) - PixelFormatID paletteFormat = nullptr; // パレットエントリのフォーマット - - // カラーキー情報(toStraight後にin-placeで適用) - uint32_t colorKeyRGBA8 = 0; // カラーキー比較値(RGBA8、alpha込み) - uint32_t colorKeyReplace = 0; // カラーキー差し替え値(通常は透明黒0) - // colorKeyRGBA8 == colorKeyReplace の場合は無効 - - uint16_t paletteColorCount = 0; // パレットエントリ数 - uint8_t alphaMultiplier = 255; // アルファ係数(1 byte)AlphaNodeで使用 - uint8_t pixelOffsetInByte = - 0; // bit-packed用: 1バイト内でのピクセル位置 (0 - PixelsPerByte-1) - // Index1: 0-7, Index2: 0-3, Index4: 0-1 - - // デフォルトコンストラクタ - constexpr PixelAuxInfo() = default; - - // アルファ係数指定 - constexpr explicit PixelAuxInfo(uint8_t alpha) : alphaMultiplier(alpha) {} - - // カラーキー指定 - constexpr PixelAuxInfo(uint32_t keyRGBA8, uint32_t replaceRGBA8) - : colorKeyRGBA8(keyRGBA8), colorKeyReplace(replaceRGBA8) {} + // パレット情報(インデックスフォーマット用) + const void *palette = nullptr; // パレットデータポインタ(非所有) + PixelFormatID paletteFormat = nullptr; // パレットエントリのフォーマット + + // カラーキー情報(toStraight後にin-placeで適用) + uint32_t colorKeyRGBA8 = 0; // カラーキー比較値(RGBA8、alpha込み) + uint32_t colorKeyReplace = 0; // カラーキー差し替え値(通常は透明黒0) + // colorKeyRGBA8 == colorKeyReplace の場合は無効 + + uint16_t paletteColorCount = 0; // パレットエントリ数 + uint8_t alphaMultiplier = 255; // アルファ係数(1 byte)AlphaNodeで使用 + uint8_t pixelOffsetInByte = 0; // bit-packed用: 1バイト内でのピクセル位置 (0 - PixelsPerByte-1) + // Index1: 0-7, Index2: 0-3, Index4: 0-1 + + // デフォルトコンストラクタ + constexpr PixelAuxInfo() = default; + + // アルファ係数指定 + constexpr explicit PixelAuxInfo(uint8_t alpha) : alphaMultiplier(alpha) + { + } + + // カラーキー指定 + constexpr PixelAuxInfo(uint32_t keyRGBA8, uint32_t replaceRGBA8) + : colorKeyRGBA8(keyRGBA8), colorKeyReplace(replaceRGBA8) + { + } }; // ======================================================================== @@ -137,15 +137,19 @@ struct PixelAuxInfo { // struct PaletteData { - const void *data = nullptr; // パレットデータ(非所有) - PixelFormatID format = nullptr; // 各エントリのフォーマット - uint16_t colorCount = 0; // エントリ数 + const void *data = nullptr; // パレットデータ(非所有) + PixelFormatID format = nullptr; // 各エントリのフォーマット + uint16_t colorCount = 0; // エントリ数 - constexpr PaletteData() = default; - constexpr PaletteData(const void *d, PixelFormatID f, uint16_t c) - : data(d), format(f), colorCount(c) {} + constexpr PaletteData() = default; + constexpr PaletteData(const void *d, PixelFormatID f, uint16_t c) : data(d), format(f), colorCount(c) + { + } - explicit operator bool() const { return data != nullptr; } + explicit operator bool() const + { + return data != nullptr; + } }; // ======================================================================== @@ -154,15 +158,15 @@ struct PaletteData { // ビット順序(bit-packed形式用) enum class BitOrder { - MSBFirst, // 最上位ビットが先(例: 1bit bitmap) - LSBFirst // 最下位ビットが先 + MSBFirst, // 最上位ビットが先(例: 1bit bitmap) + LSBFirst // 最下位ビットが先 }; // バイト順序(multi-byte形式用) enum class ByteOrder { - BigEndian, // ビッグエンディアン(ネットワークバイトオーダー) - LittleEndian, // リトルエンディアン(x86等) - Native // ネイティブ(プラットフォーム依存) + BigEndian, // ビッグエンディアン(ネットワークバイトオーダー) + LittleEndian, // リトルエンディアン(x86等) + Native // ネイティブ(プラットフォーム依存) }; // ======================================================================== @@ -170,74 +174,71 @@ enum class ByteOrder { // ======================================================================== struct PixelFormatDescriptor { - // ======================================================================== - // 変換関数の型定義 - // ======================================================================== - // 統一シグネチャ: void(*)(void* dst, const void* src, size_t pixelCount, - // const PixelAuxInfo* aux) - - // Straight形式(RGBA8_Straight)との相互変換 - using ConvertFunc = void (*)(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *aux); - using ToStraightFunc = ConvertFunc; - using FromStraightFunc = ConvertFunc; - - // インデックス展開関数(インデックス値 → - // パレットフォーマットのピクセルデータ) aux->palette, aux->paletteFormat - // を参照してインデックスをパレットエントリに展開 - // 出力はパレットフォーマット(RGBA8とは限らない) - using ExpandIndexFunc = ConvertFunc; - - // BlendUnderStraightFunc: - // srcフォーマットからStraight形式(RGBA8)のdstへunder合成 - // - dst が不透明なら何もしない(スキップ) - // - dst が透明なら単純コピー - // - dst が半透明ならunder合成(unpremultiply含む) - using BlendUnderStraightFunc = ConvertFunc; - - // SwapEndianFunc: エンディアン違いの兄弟フォーマットとの変換 - using SwapEndianFunc = ConvertFunc; - - // ======================================================================== - // メンバ(アライメント効率順: ポインタ → 4byte → 2byte → 1byte) - // ======================================================================== - - // フォーマット名 - const char *name; - - // 変換関数ポインタ(ダイレクトカラー用) - ToStraightFunc toStraight; - FromStraightFunc fromStraight; - ExpandIndexFunc expandIndex; // 非インデックスフォーマットでは nullptr - BlendUnderStraightFunc blendUnderStraight; // 未実装の場合は nullptr - - // エンディアン変換(兄弟フォーマットがある場合) - const PixelFormatDescriptor - *siblingEndian; // エンディアン違いの兄弟(なければnullptr) - SwapEndianFunc swapEndian; // バイトスワップ関数 - - // DDA転写関数 - CopyRowDDA_Func copyRowDDA; // DDA方式の行転写(nullptrなら未対応) - CopyQuadDDA_Func - copyQuadDDA; // DDA方式の4ピクセル抽出(バイリニア用、nullptrなら未対応) - - // エンディアン情報 - BitOrder bitOrder; - ByteOrder byteOrder; - - // パレット情報(インデックスカラーの場合) - uint16_t maxPaletteSize; - - // 基本情報 - uint8_t bitsPerPixel; // ピクセルあたりのビット数 - uint8_t bytesPerPixel; // ピクセルあたりのバイト数(切り上げ) - uint8_t pixelsPerUnit; // 1ユニットあたりのピクセル数 - uint8_t bytesPerUnit; // 1ユニットあたりのバイト数 - uint8_t channelCount; // チャンネル総数 - - // フラグ - bool hasAlpha; - bool isIndexed; + // ======================================================================== + // 変換関数の型定義 + // ======================================================================== + // 統一シグネチャ: void(*)(void* dst, const void* src, size_t pixelCount, + // const PixelAuxInfo* aux) + + // Straight形式(RGBA8_Straight)との相互変換 + using ConvertFunc = void (*)(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *aux); + using ToStraightFunc = ConvertFunc; + using FromStraightFunc = ConvertFunc; + + // インデックス展開関数(インデックス値 → + // パレットフォーマットのピクセルデータ) aux->palette, aux->paletteFormat + // を参照してインデックスをパレットエントリに展開 + // 出力はパレットフォーマット(RGBA8とは限らない) + using ExpandIndexFunc = ConvertFunc; + + // BlendUnderStraightFunc: + // srcフォーマットからStraight形式(RGBA8)のdstへunder合成 + // - dst が不透明なら何もしない(スキップ) + // - dst が透明なら単純コピー + // - dst が半透明ならunder合成(unpremultiply含む) + using BlendUnderStraightFunc = ConvertFunc; + + // SwapEndianFunc: エンディアン違いの兄弟フォーマットとの変換 + using SwapEndianFunc = ConvertFunc; + + // ======================================================================== + // メンバ(アライメント効率順: ポインタ → 4byte → 2byte → 1byte) + // ======================================================================== + + // フォーマット名 + const char *name; + + // 変換関数ポインタ(ダイレクトカラー用) + ToStraightFunc toStraight; + FromStraightFunc fromStraight; + ExpandIndexFunc expandIndex; // 非インデックスフォーマットでは nullptr + BlendUnderStraightFunc blendUnderStraight; // 未実装の場合は nullptr + + // エンディアン変換(兄弟フォーマットがある場合) + const PixelFormatDescriptor *siblingEndian; // エンディアン違いの兄弟(なければnullptr) + SwapEndianFunc swapEndian; // バイトスワップ関数 + + // DDA転写関数 + CopyRowDDA_Func copyRowDDA; // DDA方式の行転写(nullptrなら未対応) + CopyQuadDDA_Func copyQuadDDA; // DDA方式の4ピクセル抽出(バイリニア用、nullptrなら未対応) + + // エンディアン情報 + BitOrder bitOrder; + ByteOrder byteOrder; + + // パレット情報(インデックスカラーの場合) + uint16_t maxPaletteSize; + + // 基本情報 + uint8_t bitsPerPixel; // ピクセルあたりのビット数 + uint8_t bytesPerPixel; // ピクセルあたりのバイト数(切り上げ) + uint8_t pixelsPerUnit; // 1ユニットあたりのピクセル数 + uint8_t bytesPerUnit; // 1ユニットあたりのバイト数 + uint8_t channelCount; // チャンネル総数 + + // フラグ + bool hasAlpha; + bool isIndexed; }; // ======================================================================== @@ -249,34 +250,24 @@ namespace detail { // BytesPerPixel別 DDA転写関数(前方宣言) // 実装は dda.h で提供(FLEXIMG_IMPLEMENTATION部) -void copyRowDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyRowDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyRowDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); +void copyRowDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyRowDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyRowDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); // BytesPerPixel別 DDA 4ピクセル抽出関数(前方宣言) -void copyQuadDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyQuadDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyQuadDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); -void copyQuadDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); +void copyQuadDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyQuadDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyQuadDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); +void copyQuadDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); // BitsPerPixel別 bit-packed DDA転写関数(前方宣言) // 実装は dda.h で提供(bit_packed_index.h インクルード後) template -void copyRowDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); +void copyRowDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); template -void copyQuadDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param); +void copyQuadDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); // 8bit LUT → Nbit 変換(4ピクセル単位展開) // T = uint32_t: rgb332_toStraight, index8_expandIndex (bpc==4) 等で共用 @@ -285,19 +276,19 @@ template void lut8toN(T *d, const uint8_t *s, size_t pixelCount, const T *lut); // 便利エイリアス -inline void lut8to32(uint32_t *d, const uint8_t *s, size_t pixelCount, - const uint32_t *lut) { - lut8toN(d, s, pixelCount, lut); +inline void lut8to32(uint32_t *d, const uint8_t *s, size_t pixelCount, const uint32_t *lut) +{ + lut8toN(d, s, pixelCount, lut); } -inline void lut8to16(uint16_t *d, const uint8_t *s, size_t pixelCount, - const uint16_t *lut) { - lut8toN(d, s, pixelCount, lut); +inline void lut8to16(uint16_t *d, const uint8_t *s, size_t pixelCount, const uint16_t *lut) +{ + lut8toN(d, s, pixelCount, lut); } -} // namespace detail -} // namespace pixel_format +} // namespace detail +} // namespace pixel_format -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ------------------------------------------------------------------------ // 内部ヘルパー関数(実装部) @@ -309,47 +300,45 @@ namespace pixel_format { namespace detail { template -void lut8toN(T *d, const uint8_t *s, size_t pixelCount, const T *lut) { - while (pixelCount & 3) { - auto v0 = s[0]; - ++s; - auto l0 = lut[v0]; - --pixelCount; - d[0] = l0; - ++d; - } - pixelCount >>= 2; - if (pixelCount == 0) - return; - do { - auto v0 = s[0]; - auto v1 = s[1]; - auto v2 = s[2]; - auto v3 = s[3]; - s += 4; - auto l0 = lut[v0]; - auto l1 = lut[v1]; - auto l2 = lut[v2]; - auto l3 = lut[v3]; - d[0] = l0; - d[1] = l1; - d[2] = l2; - d[3] = l3; - d += 4; - } while (--pixelCount); +void lut8toN(T *d, const uint8_t *s, size_t pixelCount, const T *lut) +{ + while (pixelCount & 3) { + auto v0 = s[0]; + ++s; + auto l0 = lut[v0]; + --pixelCount; + d[0] = l0; + ++d; + } + pixelCount >>= 2; + if (pixelCount == 0) return; + do { + auto v0 = s[0]; + auto v1 = s[1]; + auto v2 = s[2]; + auto v3 = s[3]; + s += 4; + auto l0 = lut[v0]; + auto l1 = lut[v1]; + auto l2 = lut[v2]; + auto l3 = lut[v3]; + d[0] = l0; + d[1] = l1; + d[2] = l2; + d[3] = l3; + d += 4; + } while (--pixelCount); } // 明示的インスタンス化(非inlineを維持) -template void lut8toN(uint16_t *, const uint8_t *, size_t, - const uint16_t *); -template void lut8toN(uint32_t *, const uint8_t *, size_t, - const uint32_t *); +template void lut8toN(uint16_t *, const uint8_t *, size_t, const uint16_t *); +template void lut8toN(uint32_t *, const uint8_t *, size_t, const uint32_t *); -} // namespace detail -} // namespace pixel_format -} // namespace FLEXIMG_NAMESPACE +} // namespace detail +} // namespace pixel_format +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION // ======================================================================== // 各ピクセルフォーマット(個別ヘッダ) @@ -376,37 +365,33 @@ namespace FLEXIMG_NAMESPACE { // 組み込みフォーマット一覧(名前検索用) inline const PixelFormatID builtinFormats[] = { - PixelFormatIDs::RGBA8_Straight, PixelFormatIDs::RGB565_LE, - PixelFormatIDs::RGB565_BE, PixelFormatIDs::RGB332, - PixelFormatIDs::RGB888, PixelFormatIDs::BGR888, - PixelFormatIDs::Alpha8, PixelFormatIDs::Grayscale8, - PixelFormatIDs::Index8, PixelFormatIDs::Index1_MSB, - PixelFormatIDs::Index1_LSB, PixelFormatIDs::Index2_MSB, - PixelFormatIDs::Index2_LSB, PixelFormatIDs::Index4_MSB, - PixelFormatIDs::Index4_LSB, PixelFormatIDs::Grayscale1_MSB, - PixelFormatIDs::Grayscale1_LSB, PixelFormatIDs::Grayscale2_MSB, - PixelFormatIDs::Grayscale2_LSB, PixelFormatIDs::Grayscale4_MSB, - PixelFormatIDs::Grayscale4_LSB, + PixelFormatIDs::RGBA8_Straight, PixelFormatIDs::RGB565_LE, PixelFormatIDs::RGB565_BE, + PixelFormatIDs::RGB332, PixelFormatIDs::RGB888, PixelFormatIDs::BGR888, + PixelFormatIDs::Alpha8, PixelFormatIDs::Grayscale8, PixelFormatIDs::Index8, + PixelFormatIDs::Index1_MSB, PixelFormatIDs::Index1_LSB, PixelFormatIDs::Index2_MSB, + PixelFormatIDs::Index2_LSB, PixelFormatIDs::Index4_MSB, PixelFormatIDs::Index4_LSB, + PixelFormatIDs::Grayscale1_MSB, PixelFormatIDs::Grayscale1_LSB, PixelFormatIDs::Grayscale2_MSB, + PixelFormatIDs::Grayscale2_LSB, PixelFormatIDs::Grayscale4_MSB, PixelFormatIDs::Grayscale4_LSB, }; -inline constexpr size_t builtinFormatsCount = - sizeof(builtinFormats) / sizeof(builtinFormats[0]); +inline constexpr size_t builtinFormatsCount = sizeof(builtinFormats) / sizeof(builtinFormats[0]); // 名前からフォーマットを取得(見つからなければ nullptr) -inline PixelFormatID getFormatByName(const char *name) { - if (!name) - return nullptr; - for (size_t i = 0; i < builtinFormatsCount; ++i) { - if (std::strcmp(builtinFormats[i]->name, name) == 0) { - return builtinFormats[i]; +inline PixelFormatID getFormatByName(const char *name) +{ + if (!name) return nullptr; + for (size_t i = 0; i < builtinFormatsCount; ++i) { + if (std::strcmp(builtinFormats[i]->name, name) == 0) { + return builtinFormats[i]; + } } - } - return nullptr; + return nullptr; } // フォーマット名を取得 -inline const char *getFormatName(PixelFormatID formatID) { - return formatID ? formatID->name : "unknown"; +inline const char *getFormatName(PixelFormatID formatID) +{ + return formatID ? formatID->name : "unknown"; } // ======================================================================== @@ -424,55 +409,57 @@ inline const char *getFormatName(PixelFormatID formatID) { // struct FormatConverter { - // 解決済み変換関数(分岐なし) - using ConvertFunc = void (*)(void *dst, const void *src, size_t pixelCount, - const void *ctx); - ConvertFunc func = nullptr; - - // 解決済みコンテキスト(Prepare 時に確定) - struct Context { - // 解決済み関数ポインタ - PixelFormatDescriptor::ExpandIndexFunc expandIndex = nullptr; - PixelFormatDescriptor::ToStraightFunc toStraight = nullptr; - PixelFormatDescriptor::FromStraightFunc fromStraight = nullptr; - - // パレット情報(Index 展開用) - const void *palette = nullptr; - PixelFormatID paletteFormat = nullptr; - uint16_t paletteColorCount = 0; - - // フォーマット情報(memcpy パス用) - uint8_t pixelsPerUnit = 1; - uint8_t bytesPerUnit = 4; - - // カラーキー情報(toStraight後にin-placeで適用) - uint32_t colorKeyRGBA8 = 0; - uint32_t colorKeyReplace = 0; - - // BytesPerPixel情報(チャンク処理のポインタ進行用) - uint8_t srcBytesPerPixel = 0; - uint8_t dstBytesPerPixel = 0; - - // パレット展開時のBytesPerPixel(中間バッファ用) - uint8_t paletteBytesPerPixel = 0; - - // bit-packed用: 1バイト内でのピクセル位置(0 - PixelsPerByte-1) - uint8_t pixelOffsetInByte = 0; - } ctx; - - // 行変換実行(分岐なし) - void operator()(void *dst, const void *src, size_t pixelCount) const { - func(dst, src, pixelCount, &ctx); - } + // 解決済み変換関数(分岐なし) + using ConvertFunc = void (*)(void *dst, const void *src, size_t pixelCount, const void *ctx); + ConvertFunc func = nullptr; + + // 解決済みコンテキスト(Prepare 時に確定) + struct Context { + // 解決済み関数ポインタ + PixelFormatDescriptor::ExpandIndexFunc expandIndex = nullptr; + PixelFormatDescriptor::ToStraightFunc toStraight = nullptr; + PixelFormatDescriptor::FromStraightFunc fromStraight = nullptr; + + // パレット情報(Index 展開用) + const void *palette = nullptr; + PixelFormatID paletteFormat = nullptr; + uint16_t paletteColorCount = 0; + + // フォーマット情報(memcpy パス用) + uint8_t pixelsPerUnit = 1; + uint8_t bytesPerUnit = 4; + + // カラーキー情報(toStraight後にin-placeで適用) + uint32_t colorKeyRGBA8 = 0; + uint32_t colorKeyReplace = 0; + + // BytesPerPixel情報(チャンク処理のポインタ進行用) + uint8_t srcBytesPerPixel = 0; + uint8_t dstBytesPerPixel = 0; + + // パレット展開時のBytesPerPixel(中間バッファ用) + uint8_t paletteBytesPerPixel = 0; + + // bit-packed用: 1バイト内でのピクセル位置(0 - PixelsPerByte-1) + uint8_t pixelOffsetInByte = 0; + } ctx; + + // 行変換実行(分岐なし) + void operator()(void *dst, const void *src, size_t pixelCount) const + { + func(dst, src, pixelCount, &ctx); + } - explicit operator bool() const { return func != nullptr; } + explicit operator bool() const + { + return func != nullptr; + } }; // 変換パス解決関数 // srcFormat/dstFormat 間の最適な変換関数を事前解決し、FormatConverter を返す。 // チャンク処理により中間バッファはスタック上に確保されるため、アロケータ不要。 -FormatConverter resolveConverter(PixelFormatID srcFormat, - PixelFormatID dstFormat, +FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstFormat, const PixelAuxInfo *srcAux = nullptr); // ======================================================================== @@ -487,20 +474,20 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, // // 内部で resolveConverter を使用して最適な変換パスを解決する。 // 中間バッファが必要な場合は DefaultAllocator 経由で一時確保される。 -inline void convertFormat(const void *src, PixelFormatID srcFormat, void *dst, - PixelFormatID dstFormat, int_fast16_t pixelCount, - const PixelAuxInfo *srcAux = nullptr, - const PixelAuxInfo *dstAux = nullptr) { - (void)dstAux; // 現在の全呼び出し箇所で未使用 - auto converter = resolveConverter(srcFormat, dstFormat, srcAux); - if (converter) { - converter(dst, src, pixelCount); - } +inline void convertFormat(const void *src, PixelFormatID srcFormat, void *dst, PixelFormatID dstFormat, + int_fast16_t pixelCount, const PixelAuxInfo *srcAux = nullptr, + const PixelAuxInfo *dstAux = nullptr) +{ + (void)dstAux; // 現在の全呼び出し箇所で未使用 + auto converter = resolveConverter(srcFormat, dstFormat, srcAux); + if (converter) { + converter(dst, src, pixelCount); + } } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // FormatConverter 実装(FLEXIMG_IMPLEMENTATION ガード内) #include "pixel_format/format_converter.h" -#endif // FLEXIMG_PIXEL_FORMAT_H +#endif // FLEXIMG_PIXEL_FORMAT_H diff --git a/src/fleximg/image/pixel_format/alpha8.h b/src/fleximg/image/pixel_format/alpha8.h index b6892f3..d134861 100644 --- a/src/fleximg/image/pixel_format/alpha8.h +++ b/src/fleximg/image/pixel_format/alpha8.h @@ -18,7 +18,7 @@ namespace PixelFormatIDs { inline const PixelFormatID Alpha8 = &BuiltinFormats::Alpha8; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -34,29 +34,29 @@ namespace FLEXIMG_NAMESPACE { // ======================================================================== // Alpha8 → RGBA8_Straight(可視化のため全チャンネルにアルファ値を展開) -static void alpha8_toStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(Alpha8, ToStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - for (size_t i = 0; i < pixelCount; ++i) { - uint8_t alpha = s[i]; - d[i * 4 + 0] = alpha; // R - d[i * 4 + 1] = alpha; // G - d[i * 4 + 2] = alpha; // B - d[i * 4 + 3] = alpha; // A - } +static void alpha8_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(Alpha8, ToStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + for (size_t i = 0; i < pixelCount; ++i) { + uint8_t alpha = s[i]; + d[i * 4 + 0] = alpha; // R + d[i * 4 + 1] = alpha; // G + d[i * 4 + 2] = alpha; // B + d[i * 4 + 3] = alpha; // A + } } // RGBA8_Straight → Alpha8(Aチャンネルのみ抽出) -static void alpha8_fromStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(Alpha8, FromStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - for (size_t i = 0; i < pixelCount; ++i) { - d[i] = s[i * 4 + 3]; // Aチャンネル抽出 - } +static void alpha8_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(Alpha8, FromStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + for (size_t i = 0; i < pixelCount; ++i) { + d[i] = s[i * 4 + 3]; // Aチャンネル抽出 + } } // ------------------------------------------------------------------------ @@ -69,28 +69,28 @@ const PixelFormatDescriptor Alpha8 = { "Alpha8", alpha8_toStraight, alpha8_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - nullptr, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + nullptr, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - true, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + true, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_ALPHA8_H +#endif // FLEXIMG_PIXEL_FORMAT_ALPHA8_H diff --git a/src/fleximg/image/pixel_format/dda.h b/src/fleximg/image/pixel_format/dda.h index 96d612e..018ae3a 100644 --- a/src/fleximg/image/pixel_format/dda.h +++ b/src/fleximg/image/pixel_format/dda.h @@ -43,229 +43,218 @@ namespace detail { // BytesPerPixel → ネイティブ型マッピング(ロード・ストア分離用) // 1, 2, 4 バイトはネイティブ型で直接ロード・ストア可能 // 3 バイトはネイティブ型が存在しないため byte 単位で処理 -template struct PixelType {}; -template <> struct PixelType<1> { - using type = uint8_t; +template +struct PixelType {}; +template <> +struct PixelType<1> { + using type = uint8_t; }; -template <> struct PixelType<2> { - using type = uint16_t; +template <> +struct PixelType<2> { + using type = uint16_t; }; -template <> struct PixelType<4> { - using type = uint32_t; +template <> +struct PixelType<4> { + using type = uint32_t; }; // DDA行転写: Y座標一定パス(ソース行が同一の場合) // srcRowBase = srcData + sy * srcStride(呼び出し前に計算済み) // 4ピクセル単位展開でループオーバーヘッドを削減 template -void copyRowDDA_ConstY(uint8_t *__restrict__ dstRow, - const uint8_t *__restrict__ srcData, int_fast16_t count, - const DDAParam *param) { - int_fixed srcX = param->srcX; - const int_fixed incrX = param->incrX; - const int32_t srcStride = param->srcStride; - const uint8_t *srcRowBase = - srcData + - static_cast((param->srcY >> INT_FIXED_SHIFT) * srcStride); - - // 端数を先に処理し、4ピクセルループを最後に連続実行する - if constexpr (BytesPerPixel == 3) { - if (count & 1) { - // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) - size_t s0 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; - uint8_t p00 = srcRowBase[s0], p01 = srcRowBase[s0 + 1], - p02 = srcRowBase[s0 + 2]; - srcX += incrX; - dstRow[0] = p00; - dstRow[1] = p01; - dstRow[2] = p02; - dstRow += BytesPerPixel; - } - count >>= 1; - while (count--) { - // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) - size_t s0 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; - uint8_t p00 = srcRowBase[s0], p01 = srcRowBase[s0 + 1], - p02 = srcRowBase[s0 + 2]; - srcX += incrX; - dstRow[0] = p00; - dstRow[1] = p01; - dstRow[2] = p02; - - size_t s1 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; - uint8_t p10 = srcRowBase[s1], p11 = srcRowBase[s1 + 1], - p12 = srcRowBase[s1 + 2]; - srcX += incrX; - dstRow[3] = p10; - dstRow[4] = p11; - dstRow[5] = p12; - - dstRow += BytesPerPixel * 2; - } - } else { - using T = typename PixelType::type; - auto src = reinterpret_cast(srcRowBase); - auto dst = reinterpret_cast(dstRow); - int_fast16_t remainder = count & 3; - for (int_fast16_t i = 0; i < remainder; i++) { - // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 - auto p0 = src[srcX >> INT_FIXED_SHIFT]; - srcX += incrX; - dst[0] = p0; - dst += 1; - } - int_fast16_t count4 = count >> 2; - for (int_fast16_t i = 0; i < count4; i++) { - // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 - auto p0 = src[srcX >> INT_FIXED_SHIFT]; - srcX += incrX; - auto p1 = src[srcX >> INT_FIXED_SHIFT]; - srcX += incrX; - auto p2 = src[srcX >> INT_FIXED_SHIFT]; - srcX += incrX; - auto p3 = src[srcX >> INT_FIXED_SHIFT]; - srcX += incrX; - dst[0] = p0; - dst[1] = p1; - dst[2] = p2; - dst[3] = p3; - dst += 4; +void copyRowDDA_ConstY(uint8_t *__restrict__ dstRow, const uint8_t *__restrict__ srcData, int_fast16_t count, + const DDAParam *param) +{ + int_fixed srcX = param->srcX; + const int_fixed incrX = param->incrX; + const int32_t srcStride = param->srcStride; + const uint8_t *srcRowBase = srcData + static_cast((param->srcY >> INT_FIXED_SHIFT) * srcStride); + + // 端数を先に処理し、4ピクセルループを最後に連続実行する + if constexpr (BytesPerPixel == 3) { + if (count & 1) { + // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) + size_t s0 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; + uint8_t p00 = srcRowBase[s0], p01 = srcRowBase[s0 + 1], p02 = srcRowBase[s0 + 2]; + srcX += incrX; + dstRow[0] = p00; + dstRow[1] = p01; + dstRow[2] = p02; + dstRow += BytesPerPixel; + } + count >>= 1; + while (count--) { + // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) + size_t s0 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; + uint8_t p00 = srcRowBase[s0], p01 = srcRowBase[s0 + 1], p02 = srcRowBase[s0 + 2]; + srcX += incrX; + dstRow[0] = p00; + dstRow[1] = p01; + dstRow[2] = p02; + + size_t s1 = static_cast(srcX >> INT_FIXED_SHIFT) * 3; + uint8_t p10 = srcRowBase[s1], p11 = srcRowBase[s1 + 1], p12 = srcRowBase[s1 + 2]; + srcX += incrX; + dstRow[3] = p10; + dstRow[4] = p11; + dstRow[5] = p12; + + dstRow += BytesPerPixel * 2; + } + } else { + using T = typename PixelType::type; + auto src = reinterpret_cast(srcRowBase); + auto dst = reinterpret_cast(dstRow); + int_fast16_t remainder = count & 3; + for (int_fast16_t i = 0; i < remainder; i++) { + // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 + auto p0 = src[srcX >> INT_FIXED_SHIFT]; + srcX += incrX; + dst[0] = p0; + dst += 1; + } + int_fast16_t count4 = count >> 2; + for (int_fast16_t i = 0; i < count4; i++) { + // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 + auto p0 = src[srcX >> INT_FIXED_SHIFT]; + srcX += incrX; + auto p1 = src[srcX >> INT_FIXED_SHIFT]; + srcX += incrX; + auto p2 = src[srcX >> INT_FIXED_SHIFT]; + srcX += incrX; + auto p3 = src[srcX >> INT_FIXED_SHIFT]; + srcX += incrX; + dst[0] = p0; + dst[1] = p1; + dst[2] = p2; + dst[3] = p3; + dst += 4; + } } - } } // DDA行転写: X座標一定パス(ソース列が同一の場合) // srcColBase = srcData + sx * BytesPerPixel(呼び出し前に加算済み) // 4ピクセル単位展開でループオーバーヘッドを削減 template -void copyRowDDA_ConstX(uint8_t *__restrict__ dstRow, - const uint8_t *__restrict__ srcData, int_fast16_t count, - const DDAParam *param) { - int_fixed srcY = param->srcY; - const int_fixed incrY = param->incrY; - const int32_t srcStride = param->srcStride; - const uint8_t *srcColBase = - srcData + static_cast((param->srcX >> INT_FIXED_SHIFT) * - static_cast(BytesPerPixel)); - - int32_t sy; - // 端数を先に処理し、4ピクセルループを最後に連続実行する - if constexpr (BytesPerPixel == 3) { - while (count--) { - // BytesPerPixel==3: byte単位でピクセルごとにロード・ストア - sy = srcY >> INT_FIXED_SHIFT; - const uint8_t *r = srcColBase + static_cast(sy * srcStride); - auto p0 = r[0], p1 = r[1], p2 = r[2]; - srcY += incrY; - dstRow[0] = p0; - dstRow[1] = p1; - dstRow[2] = p2; - dstRow += BytesPerPixel; - } - } else { - using T = typename PixelType::type; - auto dst = reinterpret_cast(dstRow); - int_fast16_t remain = count & 3; - while (remain--) { - sy = srcY >> INT_FIXED_SHIFT; - auto p = *reinterpret_cast( - srcColBase + static_cast(sy * srcStride)); - srcY += incrY; - dst[0] = p; - dst += 1; - } +void copyRowDDA_ConstX(uint8_t *__restrict__ dstRow, const uint8_t *__restrict__ srcData, int_fast16_t count, + const DDAParam *param) +{ + int_fixed srcY = param->srcY; + const int_fixed incrY = param->incrY; + const int32_t srcStride = param->srcStride; + const uint8_t *srcColBase = + srcData + static_cast((param->srcX >> INT_FIXED_SHIFT) * static_cast(BytesPerPixel)); + + int32_t sy; + // 端数を先に処理し、4ピクセルループを最後に連続実行する + if constexpr (BytesPerPixel == 3) { + while (count--) { + // BytesPerPixel==3: byte単位でピクセルごとにロード・ストア + sy = srcY >> INT_FIXED_SHIFT; + const uint8_t *r = srcColBase + static_cast(sy * srcStride); + auto p0 = r[0], p1 = r[1], p2 = r[2]; + srcY += incrY; + dstRow[0] = p0; + dstRow[1] = p1; + dstRow[2] = p2; + dstRow += BytesPerPixel; + } + } else { + using T = typename PixelType::type; + auto dst = reinterpret_cast(dstRow); + int_fast16_t remain = count & 3; + while (remain--) { + sy = srcY >> INT_FIXED_SHIFT; + auto p = *reinterpret_cast(srcColBase + static_cast(sy * srcStride)); + srcY += incrY; + dst[0] = p; + dst += 1; + } - count >>= 2; - while (count--) { - // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 - sy = srcY >> INT_FIXED_SHIFT; - auto p0 = *reinterpret_cast( - srcColBase + static_cast(sy * srcStride)); - srcY += incrY; - sy = srcY >> INT_FIXED_SHIFT; - auto p1 = *reinterpret_cast( - srcColBase + static_cast(sy * srcStride)); - srcY += incrY; - dst[0] = p0; - dst[1] = p1; - sy = srcY >> INT_FIXED_SHIFT; - auto p2 = *reinterpret_cast( - srcColBase + static_cast(sy * srcStride)); - srcY += incrY; - sy = srcY >> INT_FIXED_SHIFT; - auto p3 = *reinterpret_cast( - srcColBase + static_cast(sy * srcStride)); - srcY += incrY; - dst[2] = p2; - dst[3] = p3; - dst += 4; + count >>= 2; + while (count--) { + // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 + sy = srcY >> INT_FIXED_SHIFT; + auto p0 = *reinterpret_cast(srcColBase + static_cast(sy * srcStride)); + srcY += incrY; + sy = srcY >> INT_FIXED_SHIFT; + auto p1 = *reinterpret_cast(srcColBase + static_cast(sy * srcStride)); + srcY += incrY; + dst[0] = p0; + dst[1] = p1; + sy = srcY >> INT_FIXED_SHIFT; + auto p2 = *reinterpret_cast(srcColBase + static_cast(sy * srcStride)); + srcY += incrY; + sy = srcY >> INT_FIXED_SHIFT; + auto p3 = *reinterpret_cast(srcColBase + static_cast(sy * srcStride)); + srcY += incrY; + dst[2] = p2; + dst[3] = p3; + dst += 4; + } } - } } // DDA行転写の汎用実装(両方非ゼロ、回転を含む変換) // 4ピクセル単位展開でループオーバーヘッドを削減 template -void copyRowDDA_Impl(uint8_t *__restrict__ dstRow, - const uint8_t *__restrict__ srcData, int_fast16_t count, - const DDAParam *param) { - int_fixed srcY = param->srcY; - int_fixed srcX = param->srcX; - const int_fixed incrY = param->incrY; - const int_fixed incrX = param->incrX; - const int32_t srcStride = param->srcStride; - - int32_t sx, sy; - // 端数を先に処理し、4ピクセルループを最後に連続実行する - if constexpr (BytesPerPixel == 3) { - while (count--) { - // BytesPerPixel==3: byte単位でピクセルごとにロード・ストア - sx = srcX >> INT_FIXED_SHIFT; - sy = srcY >> INT_FIXED_SHIFT; - const uint8_t *r0 = - srcData + static_cast(sy * srcStride + sx * 3); - uint8_t p00 = r0[0], p01 = r0[1], p02 = r0[2]; - srcX += incrX; - srcY += incrY; - dstRow[0] = p00; - dstRow[1] = p01; - dstRow[2] = p02; - dstRow += BytesPerPixel; - } - } else { - using T = typename PixelType::type; - auto d = reinterpret_cast(dstRow); - if (count & 1) { - sx = srcX >> INT_FIXED_SHIFT; - sy = srcY >> INT_FIXED_SHIFT; - auto p = reinterpret_cast( - srcData + static_cast(sy * srcStride))[sx]; - srcX += incrX; - srcY += incrY; - d[0] = p; - d++; - } +void copyRowDDA_Impl(uint8_t *__restrict__ dstRow, const uint8_t *__restrict__ srcData, int_fast16_t count, + const DDAParam *param) +{ + int_fixed srcY = param->srcY; + int_fixed srcX = param->srcX; + const int_fixed incrY = param->incrY; + const int_fixed incrX = param->incrX; + const int32_t srcStride = param->srcStride; + + int32_t sx, sy; + // 端数を先に処理し、4ピクセルループを最後に連続実行する + if constexpr (BytesPerPixel == 3) { + while (count--) { + // BytesPerPixel==3: byte単位でピクセルごとにロード・ストア + sx = srcX >> INT_FIXED_SHIFT; + sy = srcY >> INT_FIXED_SHIFT; + const uint8_t *r0 = srcData + static_cast(sy * srcStride + sx * 3); + uint8_t p00 = r0[0], p01 = r0[1], p02 = r0[2]; + srcX += incrX; + srcY += incrY; + dstRow[0] = p00; + dstRow[1] = p01; + dstRow[2] = p02; + dstRow += BytesPerPixel; + } + } else { + using T = typename PixelType::type; + auto d = reinterpret_cast(dstRow); + if (count & 1) { + sx = srcX >> INT_FIXED_SHIFT; + sy = srcY >> INT_FIXED_SHIFT; + auto p = reinterpret_cast(srcData + static_cast(sy * srcStride))[sx]; + srcX += incrX; + srcY += incrY; + d[0] = p; + d++; + } - count >>= 1; - while (count--) { - sx = srcX >> INT_FIXED_SHIFT; - sy = srcY >> INT_FIXED_SHIFT; - auto p0 = reinterpret_cast( - srcData + static_cast(sy * srcStride))[sx]; - srcX += incrX; - srcY += incrY; - sx = srcX >> INT_FIXED_SHIFT; - sy = srcY >> INT_FIXED_SHIFT; - auto p1 = reinterpret_cast( - srcData + static_cast(sy * srcStride))[sx]; - srcX += incrX; - srcY += incrY; - // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 - d[0] = p0; - d[1] = p1; - d += 2; + count >>= 1; + while (count--) { + sx = srcX >> INT_FIXED_SHIFT; + sy = srcY >> INT_FIXED_SHIFT; + auto p0 = reinterpret_cast(srcData + static_cast(sy * srcStride))[sx]; + srcX += incrX; + srcY += incrY; + sx = srcX >> INT_FIXED_SHIFT; + sy = srcY >> INT_FIXED_SHIFT; + auto p1 = reinterpret_cast(srcData + static_cast(sy * srcStride))[sx]; + srcX += incrX; + srcY += incrY; + // BytesPerPixel 1, 2, 4: ネイティブ型でロード・ストア分離 + d[0] = p0; + d[1] = p1; + d += 2; + } } - } } // ============================================================================ @@ -276,57 +265,51 @@ void copyRowDDA_Impl(uint8_t *__restrict__ dstRow, // template -void copyRowDDA_Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, - const DDAParam *param) { - const int_fixed srcY = param->srcY; - const int_fixed incrY = param->incrY; - // ソース座標の整数部が全ピクセルで同一か判定(座標は呼び出し側で非負が保証済み) - if (0 == (((srcY & ((1 << INT_FIXED_SHIFT) - 1)) + incrY * count) >> - INT_FIXED_SHIFT)) { - // Y座標一定パス(高頻度: 回転なし拡大縮小・平行移動、微小Y変動も含む) - copyRowDDA_ConstY(dst, srcData, count, param); - return; - } - - const int_fixed srcX = param->srcX; - const int_fixed incrX = param->incrX; - if (0 == (((srcX & ((1 << INT_FIXED_SHIFT) - 1)) + incrX * count) >> - INT_FIXED_SHIFT)) { - // X座標一定パス(微小X変動も含む) - copyRowDDA_ConstX(dst, srcData, count, param); - return; - } - - // 汎用パス(回転を含む変換) - copyRowDDA_Impl(dst, srcData, count, param); +void copyRowDDA_Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + const int_fixed srcY = param->srcY; + const int_fixed incrY = param->incrY; + // ソース座標の整数部が全ピクセルで同一か判定(座標は呼び出し側で非負が保証済み) + if (0 == (((srcY & ((1 << INT_FIXED_SHIFT) - 1)) + incrY * count) >> INT_FIXED_SHIFT)) { + // Y座標一定パス(高頻度: 回転なし拡大縮小・平行移動、微小Y変動も含む) + copyRowDDA_ConstY(dst, srcData, count, param); + return; + } + + const int_fixed srcX = param->srcX; + const int_fixed incrX = param->incrX; + if (0 == (((srcX & ((1 << INT_FIXED_SHIFT) - 1)) + incrX * count) >> INT_FIXED_SHIFT)) { + // X座標一定パス(微小X変動も含む) + copyRowDDA_ConstX(dst, srcData, count, param); + return; + } + + // 汎用パス(回転を含む変換) + copyRowDDA_Impl(dst, srcData, count, param); } // 明示的インスタンス化(各フォーマットから参照される) -template void copyRowDDA_Byte<1>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyRowDDA_Byte<2>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyRowDDA_Byte<3>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyRowDDA_Byte<4>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); +template void copyRowDDA_Byte<1>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyRowDDA_Byte<2>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyRowDDA_Byte<3>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyRowDDA_Byte<4>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); // BytesPerPixel別の関数ポインタ取得用ラッパー(非テンプレート) -inline void copyRowDDA_1Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyRowDDA_Byte<1>(dst, srcData, count, param); +inline void copyRowDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyRowDDA_Byte<1>(dst, srcData, count, param); } -inline void copyRowDDA_2Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyRowDDA_Byte<2>(dst, srcData, count, param); +inline void copyRowDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyRowDDA_Byte<2>(dst, srcData, count, param); } -inline void copyRowDDA_3Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyRowDDA_Byte<3>(dst, srcData, count, param); +inline void copyRowDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyRowDDA_Byte<3>(dst, srcData, count, param); } -inline void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyRowDDA_Byte<4>(dst, srcData, count, param); +inline void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyRowDDA_Byte<4>(dst, srcData, count, param); } // ============================================================================ @@ -344,34 +327,34 @@ inline void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, // 4ピクセルのコピー(BytesPerPixel依存部分のみ) template -inline void copyQuadPixels(uint8_t *__restrict__ dst, const uint8_t *p00, - const uint8_t *p10, const uint8_t *p01, - const uint8_t *p11) { - if constexpr (BytesPerPixel == 3) { - dst[0] = p00[0]; - dst[1] = p00[1]; - dst[2] = p00[2]; - dst[3] = p10[0]; - dst[4] = p10[1]; - dst[5] = p10[2]; - dst[6] = p01[0]; - dst[7] = p01[1]; - dst[8] = p01[2]; - dst[9] = p11[0]; - dst[10] = p11[1]; - dst[11] = p11[2]; - } else { - using T = typename PixelType::type; - auto d = reinterpret_cast(dst); - auto d0 = *reinterpret_cast(p00); - auto d1 = *reinterpret_cast(p10); - auto d2 = *reinterpret_cast(p01); - auto d3 = *reinterpret_cast(p11); - d[0] = d0; - d[1] = d1; - d[2] = d2; - d[3] = d3; - } +inline void copyQuadPixels(uint8_t *__restrict__ dst, const uint8_t *p00, const uint8_t *p10, const uint8_t *p01, + const uint8_t *p11) +{ + if constexpr (BytesPerPixel == 3) { + dst[0] = p00[0]; + dst[1] = p00[1]; + dst[2] = p00[2]; + dst[3] = p10[0]; + dst[4] = p10[1]; + dst[5] = p10[2]; + dst[6] = p01[0]; + dst[7] = p01[1]; + dst[8] = p01[2]; + dst[9] = p11[0]; + dst[10] = p11[1]; + dst[11] = p11[2]; + } else { + using T = typename PixelType::type; + auto d = reinterpret_cast(dst); + auto d0 = *reinterpret_cast(p00); + auto d1 = *reinterpret_cast(p10); + auto d2 = *reinterpret_cast(p01); + auto d3 = *reinterpret_cast(p11); + d[0] = d0; + d[1] = d1; + d[2] = d2; + d[3] = d3; + } } // 4ピクセル抽出(DDAベース、バイリニア補間用) @@ -380,174 +363,166 @@ inline void copyQuadPixels(uint8_t *__restrict__ dst, const uint8_t *p00, // count) // fadeFlags は prepareCopyQuadDDA で事前生成済み、この関数では参照・更新しない template -void copyQuadDDA_Byte(uint8_t *__restrict__ dst, - const uint8_t *__restrict__ srcData, int_fast16_t count, - const DDAParam *param) { - constexpr size_t BPP = BytesPerPixel; - constexpr size_t QUAD_SIZE = BPP * 4; - - int_fixed srcX = param->srcX; - int_fixed srcY = param->srcY; - const int_fixed incrX = param->incrX; - const int_fixed incrY = param->incrY; - const int32_t srcStride = param->srcStride; - const int32_t srcLastX = param->srcWidth - 1; - const int32_t srcLastY = param->srcHeight - 1; - BilinearWeightXY *weightsXY = param->weightsXY; - uint8_t *edgeFlags = param->edgeFlags; - - // 全ピクセル境界チェック版(事前範囲チェックなし) - for (int_fast16_t i = 0; i < count; ++i) { - int32_t sx = srcX >> INT_FIXED_SHIFT; - int32_t sy = srcY >> INT_FIXED_SHIFT; - weightsXY[i].fx = static_cast(static_cast(srcX) >> - (INT_FIXED_SHIFT - 8)); - weightsXY[i].fy = static_cast(static_cast(srcY) >> - (INT_FIXED_SHIFT - 8)); - srcX += incrX; - srcY += incrY; - bool x_sub = static_cast(sx) < static_cast(srcLastX); - bool y_sub = static_cast(sy) < static_cast(srcLastY); - - if (x_sub && y_sub) { - const uint8_t *p = - srcData + static_cast(sy) * static_cast(srcStride) + - static_cast(sx) * BPP; - edgeFlags[i] = 0; - - if constexpr (BPP == 3) { - dst[0] = p[0]; - dst[1] = p[1]; - dst[2] = p[2]; - dst[3] = p[3]; - dst[4] = p[4]; - dst[5] = p[5]; - p += srcStride; - dst[6] = p[0]; - dst[7] = p[1]; - dst[8] = p[2]; - dst[9] = p[3]; - dst[10] = p[4]; - dst[11] = p[5]; - } else { - using T = typename PixelType::type; - auto d = reinterpret_cast(dst); - auto val0 = reinterpret_cast(p)[0]; - auto val1 = reinterpret_cast(p)[1]; - d[0] = val0; - d[1] = val1; - p += srcStride; - val0 = reinterpret_cast(p)[0]; - val1 = reinterpret_cast(p)[1]; - d[2] = val0; - d[3] = val1; - } - dst += QUAD_SIZE; - } else { - // edgeFlags生成: 境界座標からフェードフラグを導出 - uint8_t flag_x = EdgeFade_Right; - uint8_t flag_y = EdgeFade_Bottom; - if (!x_sub) { - if (sx < 0) { - sx = 0; - flag_x = EdgeFade_Left; - } - } - if (!y_sub) { - if (sy < 0) { - sy = 0; - flag_y = EdgeFade_Top; - } - } - - const uint8_t *p = - srcData + static_cast(sy) * static_cast(srcStride) + - static_cast(sx) * BPP; - - if constexpr (BPP == 3) { - auto val0 = p[0]; - auto val1 = p[1]; - auto val2 = p[2]; - dst[0] = val0; - dst[1] = val1; - dst[2] = val2; - dst[3] = val0; - dst[4] = val1; - dst[5] = val2; - dst[6] = val0; - dst[7] = val1; - dst[8] = val2; - if (x_sub) { - val0 = p[3]; - val1 = p[4]; - val2 = p[5]; - flag_x = 0; - dst[3] = val0; - dst[4] = val1; - dst[5] = val2; - } else if (y_sub) { - p += srcStride; - val0 = p[0]; - val1 = p[1]; - val2 = p[2]; - flag_y = 0; - dst[6] = val0; - dst[7] = val1; - dst[8] = val2; - } - dst[9] = val0; - dst[10] = val1; - dst[11] = val2; - } else { - using T = typename PixelType::type; - auto d = reinterpret_cast(dst); - auto val = reinterpret_cast(p)[0]; - d[0] = val; - d[1] = val; - d[2] = val; - if (x_sub) { - val = reinterpret_cast(p)[1]; - flag_x = 0; - d[1] = val; - } else if (y_sub) { - p += srcStride; - val = reinterpret_cast(p)[0]; - flag_y = 0; - d[2] = val; +void copyQuadDDA_Byte(uint8_t *__restrict__ dst, const uint8_t *__restrict__ srcData, int_fast16_t count, + const DDAParam *param) +{ + constexpr size_t BPP = BytesPerPixel; + constexpr size_t QUAD_SIZE = BPP * 4; + + int_fixed srcX = param->srcX; + int_fixed srcY = param->srcY; + const int_fixed incrX = param->incrX; + const int_fixed incrY = param->incrY; + const int32_t srcStride = param->srcStride; + const int32_t srcLastX = param->srcWidth - 1; + const int32_t srcLastY = param->srcHeight - 1; + BilinearWeightXY *weightsXY = param->weightsXY; + uint8_t *edgeFlags = param->edgeFlags; + + // 全ピクセル境界チェック版(事前範囲チェックなし) + for (int_fast16_t i = 0; i < count; ++i) { + int32_t sx = srcX >> INT_FIXED_SHIFT; + int32_t sy = srcY >> INT_FIXED_SHIFT; + weightsXY[i].fx = static_cast(static_cast(srcX) >> (INT_FIXED_SHIFT - 8)); + weightsXY[i].fy = static_cast(static_cast(srcY) >> (INT_FIXED_SHIFT - 8)); + srcX += incrX; + srcY += incrY; + bool x_sub = static_cast(sx) < static_cast(srcLastX); + bool y_sub = static_cast(sy) < static_cast(srcLastY); + + if (x_sub && y_sub) { + const uint8_t *p = + srcData + static_cast(sy) * static_cast(srcStride) + static_cast(sx) * BPP; + edgeFlags[i] = 0; + + if constexpr (BPP == 3) { + dst[0] = p[0]; + dst[1] = p[1]; + dst[2] = p[2]; + dst[3] = p[3]; + dst[4] = p[4]; + dst[5] = p[5]; + p += srcStride; + dst[6] = p[0]; + dst[7] = p[1]; + dst[8] = p[2]; + dst[9] = p[3]; + dst[10] = p[4]; + dst[11] = p[5]; + } else { + using T = typename PixelType::type; + auto d = reinterpret_cast(dst); + auto val0 = reinterpret_cast(p)[0]; + auto val1 = reinterpret_cast(p)[1]; + d[0] = val0; + d[1] = val1; + p += srcStride; + val0 = reinterpret_cast(p)[0]; + val1 = reinterpret_cast(p)[1]; + d[2] = val0; + d[3] = val1; + } + dst += QUAD_SIZE; + } else { + // edgeFlags生成: 境界座標からフェードフラグを導出 + uint8_t flag_x = EdgeFade_Right; + uint8_t flag_y = EdgeFade_Bottom; + if (!x_sub) { + if (sx < 0) { + sx = 0; + flag_x = EdgeFade_Left; + } + } + if (!y_sub) { + if (sy < 0) { + sy = 0; + flag_y = EdgeFade_Top; + } + } + + const uint8_t *p = + srcData + static_cast(sy) * static_cast(srcStride) + static_cast(sx) * BPP; + + if constexpr (BPP == 3) { + auto val0 = p[0]; + auto val1 = p[1]; + auto val2 = p[2]; + dst[0] = val0; + dst[1] = val1; + dst[2] = val2; + dst[3] = val0; + dst[4] = val1; + dst[5] = val2; + dst[6] = val0; + dst[7] = val1; + dst[8] = val2; + if (x_sub) { + val0 = p[3]; + val1 = p[4]; + val2 = p[5]; + flag_x = 0; + dst[3] = val0; + dst[4] = val1; + dst[5] = val2; + } else if (y_sub) { + p += srcStride; + val0 = p[0]; + val1 = p[1]; + val2 = p[2]; + flag_y = 0; + dst[6] = val0; + dst[7] = val1; + dst[8] = val2; + } + dst[9] = val0; + dst[10] = val1; + dst[11] = val2; + } else { + using T = typename PixelType::type; + auto d = reinterpret_cast(dst); + auto val = reinterpret_cast(p)[0]; + d[0] = val; + d[1] = val; + d[2] = val; + if (x_sub) { + val = reinterpret_cast(p)[1]; + flag_x = 0; + d[1] = val; + } else if (y_sub) { + p += srcStride; + val = reinterpret_cast(p)[0]; + flag_y = 0; + d[2] = val; + } + d[3] = val; + } + edgeFlags[i] = flag_x + flag_y; + dst += QUAD_SIZE; } - d[3] = val; - } - edgeFlags[i] = flag_x + flag_y; - dst += QUAD_SIZE; } - } } // 明示的インスタンス化 -template void copyQuadDDA_Byte<1>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyQuadDDA_Byte<2>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyQuadDDA_Byte<3>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); -template void copyQuadDDA_Byte<4>(uint8_t *, const uint8_t *, int_fast16_t, - const DDAParam *); +template void copyQuadDDA_Byte<1>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyQuadDDA_Byte<2>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyQuadDDA_Byte<3>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); +template void copyQuadDDA_Byte<4>(uint8_t *, const uint8_t *, int_fast16_t, const DDAParam *); // BytesPerPixel別の関数ポインタ取得用ラッパー(非テンプレート) -inline void copyQuadDDA_1Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyQuadDDA_Byte<1>(dst, srcData, count, param); +inline void copyQuadDDA_1Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyQuadDDA_Byte<1>(dst, srcData, count, param); } -inline void copyQuadDDA_2Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyQuadDDA_Byte<2>(dst, srcData, count, param); +inline void copyQuadDDA_2Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyQuadDDA_Byte<2>(dst, srcData, count, param); } -inline void copyQuadDDA_3Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyQuadDDA_Byte<3>(dst, srcData, count, param); +inline void copyQuadDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyQuadDDA_Byte<3>(dst, srcData, count, param); } -inline void copyQuadDDA_4Byte(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - copyQuadDDA_Byte<4>(dst, srcData, count, param); +inline void copyQuadDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + copyQuadDDA_Byte<4>(dst, srcData, count, param); } // ======================================================================== @@ -564,186 +539,171 @@ inline void copyQuadDDA_4Byte(uint8_t *dst, const uint8_t *srcData, // copyRowDDA_Bit_ConstY: Y座標一定パス(バルクunpack + DDAサンプリング) // ソース行のピクセル範囲を一括unpackし、バイト配列上でDDAサンプリングする template -void copyRowDDA_Bit_ConstY(uint8_t *__restrict__ dst, - const uint8_t *__restrict__ srcData, - int_fast16_t count, const DDAParam *param) { - constexpr int PixelsPerByte = 8 / BitsPerPixel; - int_fixed srcX = param->srcX; - const int_fixed incrX = param->incrX; - const int32_t sy = param->srcY >> INT_FIXED_SHIFT; - const uint8_t *srcRow = - srcData + static_cast(sy) * static_cast(param->srcStride); - - // DDAが参照するX範囲を計算 - int32_t firstSx = srcX >> INT_FIXED_SHIFT; - int32_t lastSx = (srcX + incrX * (count - 1)) >> INT_FIXED_SHIFT; - int32_t minSx = std::min(firstSx, lastSx); - int32_t maxSx = std::max(firstSx, lastSx); - int32_t unpackCount = maxSx - minSx + 1; - - // スタックバッファでバルクunpack - constexpr int StackBufSize = 256; - uint8_t stackBuf[StackBufSize]; - - if (unpackCount <= StackBufSize) { - uint8_t pixelOffset = static_cast(minSx % PixelsPerByte); - const uint8_t *srcByte = srcRow + (minSx / PixelsPerByte); - bit_packed_detail::unpackIndexBits( - stackBuf, srcByte, static_cast(unpackCount), pixelOffset); - - // DDAサンプリング(unpack済みバイト配列から読み取り) - for (int_fast16_t i = 0; i < count; ++i) { - dst[i] = stackBuf[(srcX >> INT_FIXED_SHIFT) - minSx]; - srcX += incrX; - } - } else { - // バッファに収まらない場合: per-pixel fallback - for (int_fast16_t i = 0; i < count; ++i) { - dst[i] = bit_packed_detail::readPixelDirect( - srcData, srcX >> INT_FIXED_SHIFT, sy, param->srcStride); - srcX += incrX; +void copyRowDDA_Bit_ConstY(uint8_t *__restrict__ dst, const uint8_t *__restrict__ srcData, int_fast16_t count, + const DDAParam *param) +{ + constexpr int PixelsPerByte = 8 / BitsPerPixel; + int_fixed srcX = param->srcX; + const int_fixed incrX = param->incrX; + const int32_t sy = param->srcY >> INT_FIXED_SHIFT; + const uint8_t *srcRow = srcData + static_cast(sy) * static_cast(param->srcStride); + + // DDAが参照するX範囲を計算 + int32_t firstSx = srcX >> INT_FIXED_SHIFT; + int32_t lastSx = (srcX + incrX * (count - 1)) >> INT_FIXED_SHIFT; + int32_t minSx = std::min(firstSx, lastSx); + int32_t maxSx = std::max(firstSx, lastSx); + int32_t unpackCount = maxSx - minSx + 1; + + // スタックバッファでバルクunpack + constexpr int StackBufSize = 256; + uint8_t stackBuf[StackBufSize]; + + if (unpackCount <= StackBufSize) { + uint8_t pixelOffset = static_cast(minSx % PixelsPerByte); + const uint8_t *srcByte = srcRow + (minSx / PixelsPerByte); + bit_packed_detail::unpackIndexBits(stackBuf, srcByte, static_cast(unpackCount), + pixelOffset); + + // DDAサンプリング(unpack済みバイト配列から読み取り) + for (int_fast16_t i = 0; i < count; ++i) { + dst[i] = stackBuf[(srcX >> INT_FIXED_SHIFT) - minSx]; + srcX += incrX; + } + } else { + // バッファに収まらない場合: per-pixel fallback + for (int_fast16_t i = 0; i < count; ++i) { + dst[i] = bit_packed_detail::readPixelDirect(srcData, srcX >> INT_FIXED_SHIFT, sy, + param->srcStride); + srcX += incrX; + } } - } } // copyRowDDA_Bit: bit-packed DDA転写(ConstY判定付き) template -inline void copyRowDDA_Bit(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - const int_fixed srcY = param->srcY; - const int_fixed incrY = param->incrY; - - // ConstY判定(copyRowDDA_Byte と同一ロジック): - // ソースY座標の整数部が全ピクセルで同一かチェック - if (0 == (((srcY & ((1 << INT_FIXED_SHIFT) - 1)) + incrY * count) >> - INT_FIXED_SHIFT)) { - copyRowDDA_Bit_ConstY(dst, srcData, count, param); - return; - } - - // 汎用パス(回転を含む変換: per-pixel readPixelDirect) - int_fixed srcX = param->srcX; - int_fixed srcY_var = srcY; - const int_fixed incrX = param->incrX; - const int32_t srcStride = param->srcStride; - - for (int_fast16_t i = 0; i < count; ++i) { - int32_t sx = srcX >> INT_FIXED_SHIFT; - int32_t sy = srcY_var >> INT_FIXED_SHIFT; - srcX += incrX; - srcY_var += incrY; +inline void copyRowDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + const int_fixed srcY = param->srcY; + const int_fixed incrY = param->incrY; + + // ConstY判定(copyRowDDA_Byte と同一ロジック): + // ソースY座標の整数部が全ピクセルで同一かチェック + if (0 == (((srcY & ((1 << INT_FIXED_SHIFT) - 1)) + incrY * count) >> INT_FIXED_SHIFT)) { + copyRowDDA_Bit_ConstY(dst, srcData, count, param); + return; + } + + // 汎用パス(回転を含む変換: per-pixel readPixelDirect) + int_fixed srcX = param->srcX; + int_fixed srcY_var = srcY; + const int_fixed incrX = param->incrX; + const int32_t srcStride = param->srcStride; + + for (int_fast16_t i = 0; i < count; ++i) { + int32_t sx = srcX >> INT_FIXED_SHIFT; + int32_t sy = srcY_var >> INT_FIXED_SHIFT; + srcX += incrX; + srcY_var += incrY; #ifdef FLEXIMG_DEBUG - if (param->srcWidth > 0 && param->srcHeight > 0) { - FLEXIMG_REQUIRE(sx >= 0 && sx < param->srcWidth && sy >= 0 && - sy < param->srcHeight, - "DDA out of bounds access"); - } + if (param->srcWidth > 0 && param->srcHeight > 0) { + FLEXIMG_REQUIRE(sx >= 0 && sx < param->srcWidth && sy >= 0 && sy < param->srcHeight, + "DDA out of bounds access"); + } #endif - dst[i] = bit_packed_detail::readPixelDirect( - srcData, sx, sy, srcStride); - } + dst[i] = bit_packed_detail::readPixelDirect(srcData, sx, sy, srcStride); + } } // copyQuadDDA_Bit: 2x2グリッドをピクセル単位で直接読み取り template -inline void copyQuadDDA_Bit(uint8_t *dst, const uint8_t *srcData, - int_fast16_t count, const DDAParam *param) { - // LovyanGFXスタイル: 2x2グリッドを直接読み取り - int_fixed srcX = param->srcX; - int_fixed srcY = param->srcY; - const int_fixed incrX = param->incrX; - const int_fixed incrY = param->incrY; - const int32_t srcWidth = param->srcWidth; - const int32_t srcHeight = param->srcHeight; - const int32_t srcStride = param->srcStride; - BilinearWeightXY *weightsXY = param->weightsXY; - uint8_t *edgeFlags = param->edgeFlags; - - for (int_fast16_t i = 0; i < count; ++i) { - int32_t sx = srcX >> INT_FIXED_SHIFT; - int32_t sy = srcY >> INT_FIXED_SHIFT; - - // バイリニア補間用の重み計算 - if (weightsXY) { - weightsXY[i].fx = static_cast(static_cast(srcX) >> - (INT_FIXED_SHIFT - 8)); - weightsXY[i].fy = static_cast(static_cast(srcY) >> - (INT_FIXED_SHIFT - 8)); - } +inline void copyQuadDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param) +{ + // LovyanGFXスタイル: 2x2グリッドを直接読み取り + int_fixed srcX = param->srcX; + int_fixed srcY = param->srcY; + const int_fixed incrX = param->incrX; + const int_fixed incrY = param->incrY; + const int32_t srcWidth = param->srcWidth; + const int32_t srcHeight = param->srcHeight; + const int32_t srcStride = param->srcStride; + BilinearWeightXY *weightsXY = param->weightsXY; + uint8_t *edgeFlags = param->edgeFlags; - srcX += incrX; - srcY += incrY; - - // 境界チェック(2x2グリッドが全て範囲内か) - bool x_valid = (sx >= 0 && sx + 1 < srcWidth); - bool y_valid = (sy >= 0 && sy + 1 < srcHeight); - - if (x_valid && y_valid) { - // 全て範囲内: 2x2グリッドを読み取り - dst[0] = bit_packed_detail::readPixelDirect( - srcData, sx, sy, srcStride); - dst[1] = bit_packed_detail::readPixelDirect( - srcData, sx + 1, sy, srcStride); - dst[2] = bit_packed_detail::readPixelDirect( - srcData, sx, sy + 1, srcStride); - dst[3] = bit_packed_detail::readPixelDirect( - srcData, sx + 1, sy + 1, srcStride); - if (edgeFlags) - edgeFlags[i] = 0; - } else { - // 境界外を含む: copyQuadDDA_Byte と同じロジック - uint8_t flag_x = EdgeFade_Right; - uint8_t flag_y = EdgeFade_Bottom; - - // 座標をクランプ - if (sx < 0) { - sx = 0; - flag_x = EdgeFade_Left; - } - if (sy < 0) { - sy = 0; - flag_y = EdgeFade_Top; - } - - // 基準ピクセル(クランプした座標)を読む - uint8_t val = bit_packed_detail::readPixelDirect( - srcData, sx, sy, srcStride); - - // 全て基準値で初期化 - dst[0] = val; - dst[1] = val; - dst[2] = val; - - // x方向が有効なら右隣を読む - if (x_valid) { - val = bit_packed_detail::readPixelDirect( - srcData, sx + 1, sy, srcStride); - dst[1] = val; - flag_x = 0; - } else if (y_valid) { - // x方向無効でy方向有効なら下隣を読む - val = bit_packed_detail::readPixelDirect( - srcData, sx, sy + 1, srcStride); - dst[2] = val; - flag_y = 0; - } - - dst[3] = val; // 最後に読んだ値 - - if (edgeFlags) - edgeFlags[i] = flag_x + flag_y; - } + for (int_fast16_t i = 0; i < count; ++i) { + int32_t sx = srcX >> INT_FIXED_SHIFT; + int32_t sy = srcY >> INT_FIXED_SHIFT; + + // バイリニア補間用の重み計算 + if (weightsXY) { + weightsXY[i].fx = static_cast(static_cast(srcX) >> (INT_FIXED_SHIFT - 8)); + weightsXY[i].fy = static_cast(static_cast(srcY) >> (INT_FIXED_SHIFT - 8)); + } - dst += 4; - } + srcX += incrX; + srcY += incrY; + + // 境界チェック(2x2グリッドが全て範囲内か) + bool x_valid = (sx >= 0 && sx + 1 < srcWidth); + bool y_valid = (sy >= 0 && sy + 1 < srcHeight); + + if (x_valid && y_valid) { + // 全て範囲内: 2x2グリッドを読み取り + dst[0] = bit_packed_detail::readPixelDirect(srcData, sx, sy, srcStride); + dst[1] = bit_packed_detail::readPixelDirect(srcData, sx + 1, sy, srcStride); + dst[2] = bit_packed_detail::readPixelDirect(srcData, sx, sy + 1, srcStride); + dst[3] = bit_packed_detail::readPixelDirect(srcData, sx + 1, sy + 1, srcStride); + if (edgeFlags) edgeFlags[i] = 0; + } else { + // 境界外を含む: copyQuadDDA_Byte と同じロジック + uint8_t flag_x = EdgeFade_Right; + uint8_t flag_y = EdgeFade_Bottom; + + // 座標をクランプ + if (sx < 0) { + sx = 0; + flag_x = EdgeFade_Left; + } + if (sy < 0) { + sy = 0; + flag_y = EdgeFade_Top; + } + + // 基準ピクセル(クランプした座標)を読む + uint8_t val = bit_packed_detail::readPixelDirect(srcData, sx, sy, srcStride); + + // 全て基準値で初期化 + dst[0] = val; + dst[1] = val; + dst[2] = val; + + // x方向が有効なら右隣を読む + if (x_valid) { + val = bit_packed_detail::readPixelDirect(srcData, sx + 1, sy, srcStride); + dst[1] = val; + flag_x = 0; + } else if (y_valid) { + // x方向無効でy方向有効なら下隣を読む + val = bit_packed_detail::readPixelDirect(srcData, sx, sy + 1, srcStride); + dst[2] = val; + flag_y = 0; + } + + dst[3] = val; // 最後に読んだ値 + + if (edgeFlags) edgeFlags[i] = flag_x + flag_y; + } + + dst += 4; + } } -} // namespace detail -} // namespace pixel_format -} // namespace FLEXIMG_NAMESPACE +} // namespace detail +} // namespace pixel_format +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_DDA_H +#endif // FLEXIMG_PIXEL_FORMAT_DDA_H diff --git a/src/fleximg/image/pixel_format/format_converter.h b/src/fleximg/image/pixel_format/format_converter.h index 1707012..3a994e2 100644 --- a/src/fleximg/image/pixel_format/format_converter.h +++ b/src/fleximg/image/pixel_format/format_converter.h @@ -22,37 +22,36 @@ static constexpr int MAX_BYTES_PER_PIXEL = 4; // カラーキー適用ヘルパー(toStraight後のRGBA8バッファにin-placeで適用) // ======================================================================== -static inline void applyColorKey(uint32_t *rgba8, size_t pixelCount, - uint32_t colorKey, uint32_t replace) { - if (colorKey == replace) - return; - while (pixelCount & 3) { - --pixelCount; - if (rgba8[0] == colorKey) { - rgba8[0] = replace; +static inline void applyColorKey(uint32_t *rgba8, size_t pixelCount, uint32_t colorKey, uint32_t replace) +{ + if (colorKey == replace) return; + while (pixelCount & 3) { + --pixelCount; + if (rgba8[0] == colorKey) { + rgba8[0] = replace; + } + ++rgba8; } - ++rgba8; - } - pixelCount >>= 2; - while (pixelCount--) { - auto c0 = rgba8[0]; - auto c1 = rgba8[1]; - auto c2 = rgba8[2]; - auto c3 = rgba8[3]; - if (c0 == colorKey) { - rgba8[0] = replace; + pixelCount >>= 2; + while (pixelCount--) { + auto c0 = rgba8[0]; + auto c1 = rgba8[1]; + auto c2 = rgba8[2]; + auto c3 = rgba8[3]; + if (c0 == colorKey) { + rgba8[0] = replace; + } + if (c1 == colorKey) { + rgba8[1] = replace; + } + if (c2 == colorKey) { + rgba8[2] = replace; + } + if (c3 == colorKey) { + rgba8[3] = replace; + } + rgba8 += 4; } - if (c1 == colorKey) { - rgba8[1] = replace; - } - if (c2 == colorKey) { - rgba8[2] = replace; - } - if (c3 == colorKey) { - rgba8[3] = replace; - } - rgba8 += 4; - } } // ======================================================================== @@ -60,243 +59,231 @@ static inline void applyColorKey(uint32_t *rgba8, size_t pixelCount, // ======================================================================== // 同一フォーマット: memcpy -static void fcv_memcpy(void *dst, const void *src, size_t pixelCount, - const void *ctx) { - auto *c = static_cast(ctx); - size_t units = (pixelCount + c->pixelsPerUnit - 1) / c->pixelsPerUnit; - std::memcpy(dst, src, units * c->bytesPerUnit); +static void fcv_memcpy(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + size_t units = (pixelCount + c->pixelsPerUnit - 1) / c->pixelsPerUnit; + std::memcpy(dst, src, units * c->bytesPerUnit); } // 1段階変換: toStraight フィールドに格納された関数を直接呼び出し // (swapEndian, toStraight(dst=RGBA8), fromStraight(src=RGBA8) 共通) -static void fcv_single(void *dst, const void *src, size_t pixelCount, - const void *ctx) { - auto *c = static_cast(ctx); - c->toStraight(dst, src, pixelCount, nullptr); - applyColorKey(static_cast(dst), pixelCount, c->colorKeyRGBA8, - c->colorKeyReplace); +static void fcv_single(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + c->toStraight(dst, src, pixelCount, nullptr); + applyColorKey(static_cast(dst), pixelCount, c->colorKeyRGBA8, c->colorKeyReplace); } // Index展開: パレットフォーマット == 出力フォーマット(直接展開) -static void fcv_expandIndex_direct(void *dst, const void *src, - size_t pixelCount, const void *ctx) { - auto *c = static_cast(ctx); - PixelAuxInfo aux; - aux.palette = c->palette; - aux.paletteFormat = c->paletteFormat; - aux.paletteColorCount = c->paletteColorCount; - aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 - c->expandIndex(dst, src, pixelCount, &aux); +static void fcv_expandIndex_direct(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + PixelAuxInfo aux; + aux.palette = c->palette; + aux.paletteFormat = c->paletteFormat; + aux.paletteColorCount = c->paletteColorCount; + aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 + c->expandIndex(dst, src, pixelCount, &aux); } // Index展開 + fromStraight(パレットフォーマット == RGBA8) // チャンク処理でアロケーション不要 -static void fcv_expandIndex_fromStraight(void *dst, const void *src, - size_t pixelCount, const void *ctx) { - auto *c = static_cast(ctx); - uint8_t straightBuf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; - - PixelAuxInfo aux; - aux.palette = c->palette; - aux.paletteFormat = c->paletteFormat; - aux.paletteColorCount = c->paletteColorCount; - aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 - - auto *dstPtr = static_cast(dst); - auto *srcPtr = static_cast(src); - size_t remaining = pixelCount; - - while (remaining > 0) { - size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; - c->expandIndex(straightBuf, srcPtr, chunk, &aux); - applyColorKey(reinterpret_cast(straightBuf), chunk, - c->colorKeyRGBA8, c->colorKeyReplace); - c->fromStraight(dstPtr, straightBuf, chunk, nullptr); - srcPtr += chunk * c->srcBytesPerPixel; - dstPtr += chunk * c->dstBytesPerPixel; - remaining -= chunk; - } +static void fcv_expandIndex_fromStraight(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + uint8_t straightBuf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; + + PixelAuxInfo aux; + aux.palette = c->palette; + aux.paletteFormat = c->paletteFormat; + aux.paletteColorCount = c->paletteColorCount; + aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 + + auto *dstPtr = static_cast(dst); + auto *srcPtr = static_cast(src); + size_t remaining = pixelCount; + + while (remaining > 0) { + size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; + c->expandIndex(straightBuf, srcPtr, chunk, &aux); + applyColorKey(reinterpret_cast(straightBuf), chunk, c->colorKeyRGBA8, c->colorKeyReplace); + c->fromStraight(dstPtr, straightBuf, chunk, nullptr); + srcPtr += chunk * c->srcBytesPerPixel; + dstPtr += chunk * c->dstBytesPerPixel; + remaining -= chunk; + } } // Index展開 + toStraight + fromStraight(パレットフォーマット != RGBA8, 一般) // 単一バッファでin-place処理(expandIndex出力を末尾詰めし、toStraightで先頭から上書き) -static void fcv_expandIndex_toStraight_fromStraight(void *dst, const void *src, - size_t pixelCount, - const void *ctx) { - auto *c = static_cast(ctx); - FLEXIMG_ASSERT(c->paletteBytesPerPixel <= MAX_BYTES_PER_PIXEL, - "paletteBytesPerPixel exceeds MAX_BYTES_PER_PIXEL"); - - // 単一バッファ: expandIndex出力を末尾に配置し、toStraightで先頭から上書き - uint8_t buf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; - - PixelAuxInfo aux; - aux.palette = c->palette; - aux.paletteFormat = c->paletteFormat; - aux.paletteColorCount = c->paletteColorCount; - aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 - - auto *dstPtr = static_cast(dst); - auto *srcPtr = static_cast(src); - size_t remaining = pixelCount; - - // 末尾詰めオフセット: - // toStraightが前から処理する際に上書きが発生しない位置(固定) - uint8_t *expandPtr = - buf + (MAX_BYTES_PER_PIXEL - c->paletteBytesPerPixel) * FCV_CHUNK_SIZE; - - while (remaining > 0) { - size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; - c->expandIndex(expandPtr, srcPtr, chunk, &aux); - c->toStraight(buf, expandPtr, chunk, nullptr); - applyColorKey(reinterpret_cast(buf), chunk, c->colorKeyRGBA8, - c->colorKeyReplace); - c->fromStraight(dstPtr, buf, chunk, nullptr); - srcPtr += chunk * c->srcBytesPerPixel; - dstPtr += chunk * c->dstBytesPerPixel; - remaining -= chunk; - } +static void fcv_expandIndex_toStraight_fromStraight(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + FLEXIMG_ASSERT(c->paletteBytesPerPixel <= MAX_BYTES_PER_PIXEL, "paletteBytesPerPixel exceeds MAX_BYTES_PER_PIXEL"); + + // 単一バッファ: expandIndex出力を末尾に配置し、toStraightで先頭から上書き + uint8_t buf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; + + PixelAuxInfo aux; + aux.palette = c->palette; + aux.paletteFormat = c->paletteFormat; + aux.paletteColorCount = c->paletteColorCount; + aux.pixelOffsetInByte = c->pixelOffsetInByte; // bit-packed用 + + auto *dstPtr = static_cast(dst); + auto *srcPtr = static_cast(src); + size_t remaining = pixelCount; + + // 末尾詰めオフセット: + // toStraightが前から処理する際に上書きが発生しない位置(固定) + uint8_t *expandPtr = buf + (MAX_BYTES_PER_PIXEL - c->paletteBytesPerPixel) * FCV_CHUNK_SIZE; + + while (remaining > 0) { + size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; + c->expandIndex(expandPtr, srcPtr, chunk, &aux); + c->toStraight(buf, expandPtr, chunk, nullptr); + applyColorKey(reinterpret_cast(buf), chunk, c->colorKeyRGBA8, c->colorKeyReplace); + c->fromStraight(dstPtr, buf, chunk, nullptr); + srcPtr += chunk * c->srcBytesPerPixel; + dstPtr += chunk * c->dstBytesPerPixel; + remaining -= chunk; + } } // 一般: toStraight + fromStraight(RGBA8 経由 2段階変換) // チャンク処理でアロケーション不要 -static void fcv_toStraight_fromStraight(void *dst, const void *src, - size_t pixelCount, const void *ctx) { - auto *c = static_cast(ctx); - uint8_t straightBuf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; - - auto *dstPtr = static_cast(dst); - auto *srcPtr = static_cast(src); - size_t remaining = pixelCount; - - while (remaining > 0) { - size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; - c->toStraight(straightBuf, srcPtr, chunk, nullptr); - applyColorKey(reinterpret_cast(straightBuf), chunk, - c->colorKeyRGBA8, c->colorKeyReplace); - c->fromStraight(dstPtr, straightBuf, chunk, nullptr); - srcPtr += chunk * c->srcBytesPerPixel; - dstPtr += chunk * c->dstBytesPerPixel; - remaining -= chunk; - } +static void fcv_toStraight_fromStraight(void *dst, const void *src, size_t pixelCount, const void *ctx) +{ + auto *c = static_cast(ctx); + uint8_t straightBuf[FCV_CHUNK_SIZE * MAX_BYTES_PER_PIXEL]; + + auto *dstPtr = static_cast(dst); + auto *srcPtr = static_cast(src); + size_t remaining = pixelCount; + + while (remaining > 0) { + size_t chunk = (remaining < FCV_CHUNK_SIZE) ? remaining : FCV_CHUNK_SIZE; + c->toStraight(straightBuf, srcPtr, chunk, nullptr); + applyColorKey(reinterpret_cast(straightBuf), chunk, c->colorKeyRGBA8, c->colorKeyReplace); + c->fromStraight(dstPtr, straightBuf, chunk, nullptr); + srcPtr += chunk * c->srcBytesPerPixel; + dstPtr += chunk * c->dstBytesPerPixel; + remaining -= chunk; + } } // ======================================================================== // resolveConverter 実装 // ======================================================================== -FormatConverter resolveConverter(PixelFormatID srcFormat, - PixelFormatID dstFormat, - const PixelAuxInfo *srcAux) { - FormatConverter result; - - if (!srcFormat || !dstFormat) - return result; +FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstFormat, const PixelAuxInfo *srcAux) +{ + FormatConverter result; - // BytesPerPixel情報を設定(チャンク処理のポインタ進行用) - result.ctx.srcBytesPerPixel = srcFormat->bytesPerPixel; - result.ctx.dstBytesPerPixel = dstFormat->bytesPerPixel; + if (!srcFormat || !dstFormat) return result; - // pixelOffsetInByteを伝播(bit-packed用) - if (srcAux) { - result.ctx.pixelOffsetInByte = srcAux->pixelOffsetInByte; - } - - // 同一フォーマット → memcpy - if (srcFormat == dstFormat) { - result.ctx.pixelsPerUnit = srcFormat->pixelsPerUnit; - result.ctx.bytesPerUnit = srcFormat->bytesPerUnit; - result.func = fcv_memcpy; - return result; - } + // BytesPerPixel情報を設定(チャンク処理のポインタ進行用) + result.ctx.srcBytesPerPixel = srcFormat->bytesPerPixel; + result.ctx.dstBytesPerPixel = dstFormat->bytesPerPixel; - // エンディアン兄弟 → swapEndian - if (srcFormat->siblingEndian == dstFormat && srcFormat->swapEndian) { - result.ctx.toStraight = srcFormat->swapEndian; - result.func = fcv_single; - return result; - } - - // インデックスフォーマット + パレット - if (srcFormat->expandIndex && srcAux && srcAux->palette) { - PixelFormatID palFmt = srcAux->paletteFormat; - result.ctx.palette = srcAux->palette; - result.ctx.paletteFormat = palFmt; - result.ctx.paletteColorCount = srcAux->paletteColorCount; - result.ctx.expandIndex = srcFormat->expandIndex; - - if (palFmt == dstFormat) { - // 直接展開: Index → パレットフォーマット == 出力フォーマット - result.func = fcv_expandIndex_direct; - return result; + // pixelOffsetInByteを伝播(bit-packed用) + if (srcAux) { + result.ctx.pixelOffsetInByte = srcAux->pixelOffsetInByte; } - // インデックスフォーマットのcolorKey設定(共通) - if (srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { - result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; - result.ctx.colorKeyReplace = srcAux->colorKeyReplace; + // 同一フォーマット → memcpy + if (srcFormat == dstFormat) { + result.ctx.pixelsPerUnit = srcFormat->pixelsPerUnit; + result.ctx.bytesPerUnit = srcFormat->bytesPerUnit; + result.func = fcv_memcpy; + return result; } - if (palFmt == PixelFormatIDs::RGBA8_Straight) { - // expandIndex → fromStraight - if (dstFormat->fromStraight) { - result.ctx.fromStraight = dstFormat->fromStraight; - result.func = fcv_expandIndex_fromStraight; - } - return result; + // エンディアン兄弟 → swapEndian + if (srcFormat->siblingEndian == dstFormat && srcFormat->swapEndian) { + result.ctx.toStraight = srcFormat->swapEndian; + result.func = fcv_single; + return result; } - // expandIndex → toStraight → fromStraight - if (palFmt && palFmt->toStraight && dstFormat->fromStraight) { - result.ctx.toStraight = palFmt->toStraight; - result.ctx.fromStraight = dstFormat->fromStraight; - result.ctx.paletteBytesPerPixel = - static_cast(palFmt->bytesPerPixel); - result.func = fcv_expandIndex_toStraight_fromStraight; + // インデックスフォーマット + パレット + if (srcFormat->expandIndex && srcAux && srcAux->palette) { + PixelFormatID palFmt = srcAux->paletteFormat; + result.ctx.palette = srcAux->palette; + result.ctx.paletteFormat = palFmt; + result.ctx.paletteColorCount = srcAux->paletteColorCount; + result.ctx.expandIndex = srcFormat->expandIndex; + + if (palFmt == dstFormat) { + // 直接展開: Index → パレットフォーマット == 出力フォーマット + result.func = fcv_expandIndex_direct; + return result; + } + + // インデックスフォーマットのcolorKey設定(共通) + if (srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { + result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; + result.ctx.colorKeyReplace = srcAux->colorKeyReplace; + } + + if (palFmt == PixelFormatIDs::RGBA8_Straight) { + // expandIndex → fromStraight + if (dstFormat->fromStraight) { + result.ctx.fromStraight = dstFormat->fromStraight; + result.func = fcv_expandIndex_fromStraight; + } + return result; + } + + // expandIndex → toStraight → fromStraight + if (palFmt && palFmt->toStraight && dstFormat->fromStraight) { + result.ctx.toStraight = palFmt->toStraight; + result.ctx.fromStraight = dstFormat->fromStraight; + result.ctx.paletteBytesPerPixel = static_cast(palFmt->bytesPerPixel); + result.func = fcv_expandIndex_toStraight_fromStraight; + } + return result; } - return result; - } - // src == RGBA8 → fromStraight 直接(中間バッファ不要) - if (srcFormat == PixelFormatIDs::RGBA8_Straight) { - if (dstFormat->fromStraight) { - result.ctx.toStraight = dstFormat->fromStraight; - result.func = fcv_single; + // src == RGBA8 → fromStraight 直接(中間バッファ不要) + if (srcFormat == PixelFormatIDs::RGBA8_Straight) { + if (dstFormat->fromStraight) { + result.ctx.toStraight = dstFormat->fromStraight; + result.func = fcv_single; + } + return result; } - return result; - } - - // dst == RGBA8 → toStraight 直接(中間バッファ不要) - if (dstFormat == PixelFormatIDs::RGBA8_Straight) { - if (srcFormat->toStraight) { - result.ctx.toStraight = srcFormat->toStraight; - if (srcAux && !srcFormat->hasAlpha && - srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { - result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; - result.ctx.colorKeyReplace = srcAux->colorKeyReplace; - } - result.func = fcv_single; + + // dst == RGBA8 → toStraight 直接(中間バッファ不要) + if (dstFormat == PixelFormatIDs::RGBA8_Straight) { + if (srcFormat->toStraight) { + result.ctx.toStraight = srcFormat->toStraight; + if (srcAux && !srcFormat->hasAlpha && srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { + result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; + result.ctx.colorKeyReplace = srcAux->colorKeyReplace; + } + result.func = fcv_single; + } + return result; } - return result; - } - - // 一般: toStraight + fromStraight - if (srcFormat->toStraight && dstFormat->fromStraight) { - result.ctx.toStraight = srcFormat->toStraight; - result.ctx.fromStraight = dstFormat->fromStraight; - if (srcAux && !srcFormat->hasAlpha && - srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { - result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; - result.ctx.colorKeyReplace = srcAux->colorKeyReplace; + + // 一般: toStraight + fromStraight + if (srcFormat->toStraight && dstFormat->fromStraight) { + result.ctx.toStraight = srcFormat->toStraight; + result.ctx.fromStraight = dstFormat->fromStraight; + if (srcAux && !srcFormat->hasAlpha && srcAux->colorKeyRGBA8 != srcAux->colorKeyReplace) { + result.ctx.colorKeyRGBA8 = srcAux->colorKeyRGBA8; + result.ctx.colorKeyReplace = srcAux->colorKeyReplace; + } + result.func = fcv_toStraight_fromStraight; } - result.func = fcv_toStraight_fromStraight; - } - return result; + return result; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_FORMAT_CONVERTER_H +#endif // FLEXIMG_PIXEL_FORMAT_FORMAT_CONVERTER_H diff --git a/src/fleximg/image/pixel_format/grayscale.h b/src/fleximg/image/pixel_format/grayscale.h index e62a64a..725d956 100644 --- a/src/fleximg/image/pixel_format/grayscale.h +++ b/src/fleximg/image/pixel_format/grayscale.h @@ -21,7 +21,7 @@ extern const PixelFormatDescriptor Grayscale2_MSB; extern const PixelFormatDescriptor Grayscale2_LSB; extern const PixelFormatDescriptor Grayscale4_MSB; extern const PixelFormatDescriptor Grayscale4_LSB; -} // namespace BuiltinFormats +} // namespace BuiltinFormats namespace PixelFormatIDs { inline const PixelFormatID Grayscale8 = &BuiltinFormats::Grayscale8; @@ -32,9 +32,9 @@ inline const PixelFormatID Grayscale2_MSB = &BuiltinFormats::Grayscale2_MSB; inline const PixelFormatID Grayscale2_LSB = &BuiltinFormats::Grayscale2_LSB; inline const PixelFormatID Grayscale4_MSB = &BuiltinFormats::Grayscale4_MSB; inline const PixelFormatID Grayscale4_LSB = &BuiltinFormats::Grayscale4_LSB; -} // namespace PixelFormatIDs +} // namespace PixelFormatIDs -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -50,53 +50,48 @@ namespace FLEXIMG_NAMESPACE { // ======================================================================== // Grayscale8 → RGBA8_Straight(L → R=G=B=L, A=255) -static void grayscale8_toStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(Grayscale8, ToStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - for (size_t i = 0; i < pixelCount; ++i) { - uint8_t lum = s[i]; - d[i * 4 + 0] = lum; // R - d[i * 4 + 1] = lum; // G - d[i * 4 + 2] = lum; // B - d[i * 4 + 3] = 255; // A - } +static void grayscale8_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(Grayscale8, ToStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + for (size_t i = 0; i < pixelCount; ++i) { + uint8_t lum = s[i]; + d[i * 4 + 0] = lum; // R + d[i * 4 + 1] = lum; // G + d[i * 4 + 2] = lum; // B + d[i * 4 + 3] = 255; // A + } } // RGBA8_Straight → Grayscale8(BT.601 輝度計算) -static void grayscale8_fromStraight(void *dst, const void *src, - size_t pixelCount, const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(Grayscale8, FromStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - - // BT.601: Y = 0.299*R + 0.587*G + 0.114*B - // 整数近似: (77*R + 150*G + 29*B + 128) >> 8 - - // 端数処理(1〜3ピクセル) - size_t remainder = pixelCount & 3; - while (remainder--) { - d[0] = - static_cast((77 * s[0] + 150 * s[1] + 29 * s[2] + 128) >> 8); - s += 4; - d += 1; - } - - // 4ピクセル単位でループ - pixelCount >>= 2; - while (pixelCount--) { - d[0] = - static_cast((77 * s[0] + 150 * s[1] + 29 * s[2] + 128) >> 8); - d[1] = - static_cast((77 * s[4] + 150 * s[5] + 29 * s[6] + 128) >> 8); - d[2] = - static_cast((77 * s[8] + 150 * s[9] + 29 * s[10] + 128) >> 8); - d[3] = static_cast((77 * s[12] + 150 * s[13] + 29 * s[14] + 128) >> - 8); - s += 16; - d += 4; - } +static void grayscale8_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(Grayscale8, FromStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + + // BT.601: Y = 0.299*R + 0.587*G + 0.114*B + // 整数近似: (77*R + 150*G + 29*B + 128) >> 8 + + // 端数処理(1〜3ピクセル) + size_t remainder = pixelCount & 3; + while (remainder--) { + d[0] = static_cast((77 * s[0] + 150 * s[1] + 29 * s[2] + 128) >> 8); + s += 4; + d += 1; + } + + // 4ピクセル単位でループ + pixelCount >>= 2; + while (pixelCount--) { + d[0] = static_cast((77 * s[0] + 150 * s[1] + 29 * s[2] + 128) >> 8); + d[1] = static_cast((77 * s[4] + 150 * s[5] + 29 * s[6] + 128) >> 8); + d[2] = static_cast((77 * s[8] + 150 * s[9] + 29 * s[10] + 128) >> 8); + d[3] = static_cast((77 * s[12] + 150 * s[13] + 29 * s[14] + 128) >> 8); + s += 16; + d += 4; + } } // ------------------------------------------------------------------------ @@ -109,25 +104,25 @@ const PixelFormatDescriptor Grayscale8 = { "Grayscale8", grayscale8_toStraight, grayscale8_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - nullptr, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + nullptr, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats // ======================================================================== // ビット操作ヘルパー関数(bit-packed Grayscale/Index共用) @@ -140,37 +135,34 @@ namespace bit_packed_detail { // ======================================================================== template -inline void unpackIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount, - uint8_t pixelOffset = 0) { - constexpr int PixelsPerByte = 8 / BitsPerPixel; - constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; - - // pixelOffsetは1バイト内でのピクセル位置 (0 - PixelsPerByte-1) - // 最初のバイトでの開始位置を調整 - size_t pixelIdx = pixelOffset; - size_t byteIdx = 0; - size_t dstIdx = 0; - - while (dstIdx < pixelCount) { - uint8_t b = src[byteIdx]; - size_t remainingInByte = static_cast(PixelsPerByte) - pixelIdx; - size_t pixelsToRead = (pixelCount - dstIdx < remainingInByte) - ? (pixelCount - dstIdx) - : remainingInByte; - - for (size_t j = 0; j < pixelsToRead; ++j) { - size_t bitPos = pixelIdx + j; - if constexpr (Order == BitOrder::MSBFirst) { - dst[dstIdx++] = - (b >> ((PixelsPerByte - 1 - bitPos) * BitsPerPixel)) & Mask; - } else { - dst[dstIdx++] = (b >> (bitPos * BitsPerPixel)) & Mask; - } +inline void unpackIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount, uint8_t pixelOffset = 0) +{ + constexpr int PixelsPerByte = 8 / BitsPerPixel; + constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; + + // pixelOffsetは1バイト内でのピクセル位置 (0 - PixelsPerByte-1) + // 最初のバイトでの開始位置を調整 + size_t pixelIdx = pixelOffset; + size_t byteIdx = 0; + size_t dstIdx = 0; + + while (dstIdx < pixelCount) { + uint8_t b = src[byteIdx]; + size_t remainingInByte = static_cast(PixelsPerByte) - pixelIdx; + size_t pixelsToRead = (pixelCount - dstIdx < remainingInByte) ? (pixelCount - dstIdx) : remainingInByte; + + for (size_t j = 0; j < pixelsToRead; ++j) { + size_t bitPos = pixelIdx + j; + if constexpr (Order == BitOrder::MSBFirst) { + dst[dstIdx++] = (b >> ((PixelsPerByte - 1 - bitPos) * BitsPerPixel)) & Mask; + } else { + dst[dstIdx++] = (b >> (bitPos * BitsPerPixel)) & Mask; + } + } + + ++byteIdx; + pixelIdx = 0; // 次のバイトからは先頭から読む } - - ++byteIdx; - pixelIdx = 0; // 次のバイトからは先頭から読む - } } // ======================================================================== @@ -178,26 +170,26 @@ inline void unpackIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount, // ======================================================================== template -inline void packIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount) { - constexpr size_t PixelsPerByte = 8 / BitsPerPixel; - constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; - - size_t bytes = (pixelCount + PixelsPerByte - 1) / PixelsPerByte; - for (size_t i = 0; i < bytes; ++i) { - uint8_t b = 0; - size_t pixels_in_byte = - (pixelCount >= PixelsPerByte) ? PixelsPerByte : pixelCount; - for (size_t j = 0; j < pixels_in_byte; ++j) { - if constexpr (Order == BitOrder::MSBFirst) { - b |= ((src[j] & Mask) << ((PixelsPerByte - 1 - j) * BitsPerPixel)); - } else { - b |= ((src[j] & Mask) << (j * BitsPerPixel)); - } +inline void packIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount) +{ + constexpr size_t PixelsPerByte = 8 / BitsPerPixel; + constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; + + size_t bytes = (pixelCount + PixelsPerByte - 1) / PixelsPerByte; + for (size_t i = 0; i < bytes; ++i) { + uint8_t b = 0; + size_t pixels_in_byte = (pixelCount >= PixelsPerByte) ? PixelsPerByte : pixelCount; + for (size_t j = 0; j < pixels_in_byte; ++j) { + if constexpr (Order == BitOrder::MSBFirst) { + b |= ((src[j] & Mask) << ((PixelsPerByte - 1 - j) * BitsPerPixel)); + } else { + b |= ((src[j] & Mask) << (j * BitsPerPixel)); + } + } + dst[i] = b; + src += PixelsPerByte; + pixelCount -= PixelsPerByte; } - dst[i] = b; - src += PixelsPerByte; - pixelCount -= PixelsPerByte; - } } // ======================================================================== @@ -206,27 +198,27 @@ inline void packIndexBits(uint8_t *dst, const uint8_t *src, size_t pixelCount) { // 指定座標のピクセルを bit-packed データから直接読み取り template -inline uint8_t readPixelDirect(const uint8_t *srcData, int32_t x, int32_t y, - int32_t stride) { - constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; - - // ビット単位のオフセット計算 - int32_t pixelOffsetInByte = (y * stride * 8) + (x * BitsPerPixel); - int32_t byteIdx = pixelOffsetInByte >> 3; - int32_t bitPos = pixelOffsetInByte & 7; - - uint8_t byte = srcData[byteIdx]; - - if constexpr (Order == BitOrder::MSBFirst) { - // MSBFirst: 上位ビットから読む - return (byte >> (8 - bitPos - BitsPerPixel)) & Mask; - } else { - // LSBFirst: 下位ビットから読む - return (byte >> bitPos) & Mask; - } +inline uint8_t readPixelDirect(const uint8_t *srcData, int32_t x, int32_t y, int32_t stride) +{ + constexpr uint8_t Mask = (1 << BitsPerPixel) - 1; + + // ビット単位のオフセット計算 + int32_t pixelOffsetInByte = (y * stride * 8) + (x * BitsPerPixel); + int32_t byteIdx = pixelOffsetInByte >> 3; + int32_t bitPos = pixelOffsetInByte & 7; + + uint8_t byte = srcData[byteIdx]; + + if constexpr (Order == BitOrder::MSBFirst) { + // MSBFirst: 上位ビットから読む + return (byte >> (8 - bitPos - BitsPerPixel)) & Mask; + } else { + // LSBFirst: 下位ビットから読む + return (byte >> bitPos) & Mask; + } } -} // namespace bit_packed_detail +} // namespace bit_packed_detail // ======================================================================== // GrayscaleN: bit-packed Grayscale ↔ RGBA8_Straight 変換 @@ -237,71 +229,68 @@ inline uint8_t readPixelDirect(const uint8_t *srcData, int32_t x, int32_t y, // 出力バッファ(RGBA8=4byte/pixel)の末尾にGrayscale8データをunpackし、 // スケーリング後に grayscale8_toStraight でin-place展開する template -static void grayscaleN_toStraight(void *__restrict__ dst, - const void *__restrict__ src, - size_t pixelCount, const PixelAuxInfo *aux) { - FLEXIMG_FMT_METRICS(GrayscaleN, ToStraight, pixelCount); - uint8_t *d = static_cast(dst); - const uint8_t *s = static_cast(src); - - // 末尾詰め: RGBA8出力(4byte/pixel)の後方にGrayscale8(1byte/pixel)をunpack - uint8_t *grayData = d + static_cast(pixelCount) * 3; - - const uint8_t pixelOffsetInByte = aux ? aux->pixelOffsetInByte : 0; - bit_packed_detail::unpackIndexBits( - grayData, s, pixelCount, pixelOffsetInByte); - - // スケーリング: GrayscaleN値(0-MaxVal) → 0-255 (Grayscale8相当) - constexpr int MaxVal = (1 << BitsPerPixel) - 1; - constexpr int Scale = 255 / MaxVal; - for (size_t i = 0; i < pixelCount; ++i) { - grayData[i] = static_cast(grayData[i] * Scale); - } - - // grayscale8_toStraight に委譲(in-place: grayData → d) - grayscale8_toStraight(dst, grayData, pixelCount, nullptr); +static void grayscaleN_toStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *aux) +{ + FLEXIMG_FMT_METRICS(GrayscaleN, ToStraight, pixelCount); + uint8_t *d = static_cast(dst); + const uint8_t *s = static_cast(src); + + // 末尾詰め: RGBA8出力(4byte/pixel)の後方にGrayscale8(1byte/pixel)をunpack + uint8_t *grayData = d + static_cast(pixelCount) * 3; + + const uint8_t pixelOffsetInByte = aux ? aux->pixelOffsetInByte : 0; + bit_packed_detail::unpackIndexBits(grayData, s, pixelCount, pixelOffsetInByte); + + // スケーリング: GrayscaleN値(0-MaxVal) → 0-255 (Grayscale8相当) + constexpr int MaxVal = (1 << BitsPerPixel) - 1; + constexpr int Scale = 255 / MaxVal; + for (size_t i = 0; i < pixelCount; ++i) { + grayData[i] = static_cast(grayData[i] * Scale); + } + + // grayscale8_toStraight に委譲(in-place: grayData → d) + grayscale8_toStraight(dst, grayData, pixelCount, nullptr); } // RGBA8_Straight → GrayscaleN(BT.601輝度計算 + 量子化 → bit-packed) template -static void grayscaleN_fromStraight(void *__restrict__ dst, - const void *__restrict__ src, - size_t pixelCount, const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(GrayscaleN, FromStraight, pixelCount); - constexpr size_t MaxPixelsPerByte = 8 / BitsPerPixel; - constexpr size_t ChunkSize = 64; - uint8_t grayBuf[ChunkSize]; - - const uint8_t *srcPtr = static_cast(src); - uint8_t *dstPtr = static_cast(dst); - - // 量子化シフト量 - constexpr int QuantizeShift = 8 - BitsPerPixel; - - size_t remaining = pixelCount; - while (remaining > 0) { - size_t chunk = (remaining < ChunkSize) ? remaining : ChunkSize; - - // BT.601 輝度計算 + 量子化 - for (size_t i = 0; i < chunk; ++i) { - uint_fast16_t r = srcPtr[i * 4 + 0]; - uint_fast16_t g = srcPtr[i * 4 + 1]; - uint_fast16_t b = srcPtr[i * 4 + 2]; - // BT.601: Y = (77*R + 150*G + 29*B + 128) >> 8 - uint8_t lum = - static_cast((77 * r + 150 * g + 29 * b + 128) >> 8); - // 量子化 - grayBuf[i] = lum >> QuantizeShift; +static void grayscaleN_fromStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(GrayscaleN, FromStraight, pixelCount); + constexpr size_t MaxPixelsPerByte = 8 / BitsPerPixel; + constexpr size_t ChunkSize = 64; + uint8_t grayBuf[ChunkSize]; + + const uint8_t *srcPtr = static_cast(src); + uint8_t *dstPtr = static_cast(dst); + + // 量子化シフト量 + constexpr int QuantizeShift = 8 - BitsPerPixel; + + size_t remaining = pixelCount; + while (remaining > 0) { + size_t chunk = (remaining < ChunkSize) ? remaining : ChunkSize; + + // BT.601 輝度計算 + 量子化 + for (size_t i = 0; i < chunk; ++i) { + uint_fast16_t r = srcPtr[i * 4 + 0]; + uint_fast16_t g = srcPtr[i * 4 + 1]; + uint_fast16_t b = srcPtr[i * 4 + 2]; + // BT.601: Y = (77*R + 150*G + 29*B + 128) >> 8 + uint8_t lum = static_cast((77 * r + 150 * g + 29 * b + 128) >> 8); + // 量子化 + grayBuf[i] = lum >> QuantizeShift; + } + + // パック + bit_packed_detail::packIndexBits(dstPtr, grayBuf, chunk); + + srcPtr += chunk * 4; + dstPtr += (chunk + MaxPixelsPerByte - 1) / MaxPixelsPerByte; + remaining -= chunk; } - - // パック - bit_packed_detail::packIndexBits(dstPtr, grayBuf, - chunk); - - srcPtr += chunk * 4; - dstPtr += (chunk + MaxPixelsPerByte - 1) / MaxPixelsPerByte; - remaining -= chunk; - } } // ------------------------------------------------------------------------ @@ -319,22 +308,22 @@ const PixelFormatDescriptor Grayscale1_MSB = { "Grayscale1_MSB", grayscaleN_toStraight<1, BitOrder::MSBFirst>, grayscaleN_fromStraight<1, BitOrder::MSBFirst>, - nullptr, // expandIndex - nullptr, // blendUnderStraight - &Grayscale1_LSB, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_Bit<1, BitOrder::MSBFirst>, // copyRowDDA - pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::MSBFirst>, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + &Grayscale1_LSB, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_Bit<1, BitOrder::MSBFirst>, // copyRowDDA + pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::MSBFirst>, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 1, // bitsPerPixel - 1, // bytesPerPixel - 8, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 1, // bitsPerPixel + 1, // bytesPerPixel + 8, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + false, // isIndexed }; const PixelFormatDescriptor Grayscale1_LSB = { @@ -447,10 +436,10 @@ const PixelFormatDescriptor Grayscale4_LSB = { false, }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_GRAYSCALE_H +#endif // FLEXIMG_PIXEL_FORMAT_GRAYSCALE_H diff --git a/src/fleximg/image/pixel_format/index.h b/src/fleximg/image/pixel_format/index.h index 39c56a7..b5dab3e 100644 --- a/src/fleximg/image/pixel_format/index.h +++ b/src/fleximg/image/pixel_format/index.h @@ -28,7 +28,7 @@ extern const PixelFormatDescriptor Index4_LSB; // 8-bit Index format extern const PixelFormatDescriptor Index8; -} // namespace BuiltinFormats +} // namespace BuiltinFormats namespace PixelFormatIDs { inline const PixelFormatID Index1_MSB = &BuiltinFormats::Index1_MSB; @@ -39,9 +39,9 @@ inline const PixelFormatID Index4_MSB = &BuiltinFormats::Index4_MSB; inline const PixelFormatID Index4_LSB = &BuiltinFormats::Index4_LSB; inline const PixelFormatID Index8 = &BuiltinFormats::Index8; -} // namespace PixelFormatIDs +} // namespace PixelFormatIDs -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -63,44 +63,41 @@ namespace FLEXIMG_NAMESPACE { // lut8toN は4ピクセル単位で「全読み→全書き」するため、 // src が dst の末尾に配置されている場合でも読み出しが書き込みより先行し安全。 -static void applyPaletteLUT(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *aux) { - if (!aux || !aux->palette || !aux->paletteFormat) { - std::memset(dst, 0, pixelCount); - return; - } - - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - const uint8_t *p = static_cast(aux->palette); - int_fast8_t bpc = static_cast(aux->paletteFormat->bytesPerPixel); - - if (bpc == 4) { - pixel_format::detail::lut8to32(reinterpret_cast(d), s, - pixelCount, - reinterpret_cast(p)); - } else if (bpc == 2) { - pixel_format::detail::lut8to16(reinterpret_cast(d), s, - pixelCount, - reinterpret_cast(p)); - } else { - for (size_t i = 0; i < pixelCount; ++i) { - std::memcpy(d + static_cast(i) * static_cast(bpc), - p + static_cast(s[i]) * static_cast(bpc), - static_cast(bpc)); +static void applyPaletteLUT(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *aux) +{ + if (!aux || !aux->palette || !aux->paletteFormat) { + std::memset(dst, 0, pixelCount); + return; + } + + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + const uint8_t *p = static_cast(aux->palette); + int_fast8_t bpc = static_cast(aux->paletteFormat->bytesPerPixel); + + if (bpc == 4) { + pixel_format::detail::lut8to32(reinterpret_cast(d), s, pixelCount, + reinterpret_cast(p)); + } else if (bpc == 2) { + pixel_format::detail::lut8to16(reinterpret_cast(d), s, pixelCount, + reinterpret_cast(p)); + } else { + for (size_t i = 0; i < pixelCount; ++i) { + std::memcpy(d + static_cast(i) * static_cast(bpc), + p + static_cast(s[i]) * static_cast(bpc), static_cast(bpc)); + } } - } } // ======================================================================== // Index8: パレットインデックス(8bit) → パレットフォーマットのピクセルデータ // ======================================================================== -static void index8_expandIndex(void *__restrict__ dst, - const void *__restrict__ src, size_t pixelCount, - const PixelAuxInfo *__restrict__ aux) { - FLEXIMG_FMT_METRICS(Index8, ToStraight, pixelCount); - applyPaletteLUT(dst, src, pixelCount, aux); +static void index8_expandIndex(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *__restrict__ aux) +{ + FLEXIMG_FMT_METRICS(Index8, ToStraight, pixelCount); + applyPaletteLUT(dst, src, pixelCount, aux); } // ======================================================================== @@ -112,11 +109,11 @@ static void index8_expandIndex(void *__restrict__ dst, // 将来的にパレットへの最近傍色マッチングに拡張予定。 // -static void index8_fromStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *aux) { - FLEXIMG_FMT_METRICS(Index8, FromStraight, pixelCount); - // パレットなし: グレースケール変換にフォールバック - grayscale8_fromStraight(dst, src, pixelCount, aux); +static void index8_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *aux) +{ + FLEXIMG_FMT_METRICS(Index8, FromStraight, pixelCount); + // パレットなし: グレースケール変換にフォールバック + grayscale8_fromStraight(dst, src, pixelCount, aux); } // ------------------------------------------------------------------------ @@ -127,26 +124,25 @@ namespace BuiltinFormats { const PixelFormatDescriptor Index8 = { "Index8", - grayscale8_toStraight, // toStraight - // (パレットなし時はGrayscale8にフォールバック) - index8_fromStraight, // fromStraight (BT.601 輝度抽出) - index8_expandIndex, // expandIndex - nullptr, // blendUnderStraight - nullptr, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA - pixel_format::detail:: - copyQuadDDA_1Byte, // copyQuadDDA(インデックス抽出、パレット展開はconvertFormatで実施) + grayscale8_toStraight, // toStraight + // (パレットなし時はGrayscale8にフォールバック) + index8_fromStraight, // fromStraight (BT.601 輝度抽出) + index8_expandIndex, // expandIndex + nullptr, // blendUnderStraight + nullptr, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA(インデックス抽出、パレット展開はconvertFormatで実施) BitOrder::MSBFirst, ByteOrder::Native, - 256, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - true, // isIndexed + 256, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + true, // isIndexed }; // ======================================================================== @@ -157,38 +153,36 @@ const PixelFormatDescriptor Index8 = { // 末尾詰め方式: 出力バッファ末尾にIndex8データをunpackし、 // applyPaletteLUTでin-place展開する(チャンクバッファ不要) template -static void indexN_expandIndex(void *__restrict__ dst, - const void *__restrict__ src, size_t pixelCount, - const PixelAuxInfo *__restrict__ aux) { - if (!aux || !aux->palette || !aux->paletteFormat) { - std::memset(dst, 0, pixelCount); - return; - } - - uint8_t *d = static_cast(dst); - const uint8_t *s = static_cast(src); - int palBpp = aux->paletteFormat->bytesPerPixel; - - // 末尾詰め: dstの後方にIndex8データをunpack - // palBpp=4: offset=3N, palBpp=2: offset=N, palBpp=1: offset=0 - uint8_t *indexData = - d + static_cast(pixelCount) * static_cast(palBpp - 1); - - bit_packed_detail::unpackIndexBits( - indexData, s, pixelCount, aux->pixelOffsetInByte); - - // 共通パレットLUTでin-place展開 - applyPaletteLUT(dst, indexData, pixelCount, aux); +static void indexN_expandIndex(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *__restrict__ aux) +{ + if (!aux || !aux->palette || !aux->paletteFormat) { + std::memset(dst, 0, pixelCount); + return; + } + + uint8_t *d = static_cast(dst); + const uint8_t *s = static_cast(src); + int palBpp = aux->paletteFormat->bytesPerPixel; + + // 末尾詰め: dstの後方にIndex8データをunpack + // palBpp=4: offset=3N, palBpp=2: offset=N, palBpp=1: offset=0 + uint8_t *indexData = d + static_cast(pixelCount) * static_cast(palBpp - 1); + + bit_packed_detail::unpackIndexBits(indexData, s, pixelCount, aux->pixelOffsetInByte); + + // 共通パレットLUTでin-place展開 + applyPaletteLUT(dst, indexData, pixelCount, aux); } // 変換関数: fromStraight (RGBA8 → Index, 輝度計算 + 量子化) // grayscaleN_fromStraight への委譲ラッパー template -static void indexN_fromStraight(void *__restrict__ dst, - const void *__restrict__ src, size_t pixelCount, - const PixelAuxInfo *aux) { - FLEXIMG_FMT_METRICS(IndexN, FromStraight, pixelCount); - grayscaleN_fromStraight(dst, src, pixelCount, aux); +static void indexN_fromStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *aux) +{ + FLEXIMG_FMT_METRICS(IndexN, FromStraight, pixelCount); + grayscaleN_fromStraight(dst, src, pixelCount, aux); } // ------------------------------------------------------------------------ @@ -205,21 +199,21 @@ const PixelFormatDescriptor Index1_MSB = { grayscaleN_toStraight<1, BitOrder::MSBFirst>, indexN_fromStraight<1, BitOrder::MSBFirst>, indexN_expandIndex<1, BitOrder::MSBFirst>, - nullptr, // blendUnderStraight - &Index1_LSB, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_Bit<1, BitOrder::MSBFirst>, // copyRowDDA - pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::MSBFirst>, // copyQuadDDA + nullptr, // blendUnderStraight + &Index1_LSB, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_Bit<1, BitOrder::MSBFirst>, // copyRowDDA + pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::MSBFirst>, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 2, // maxPaletteSize - 1, // bitsPerPixel - 1, // bytesPerPixel - 8, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - true, // isIndexed + 2, // maxPaletteSize + 1, // bitsPerPixel + 1, // bytesPerPixel + 8, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + true, // isIndexed }; const PixelFormatDescriptor Index1_LSB = { @@ -234,7 +228,7 @@ const PixelFormatDescriptor Index1_LSB = { pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::LSBFirst>, BitOrder::LSBFirst, ByteOrder::Native, - 2, // maxPaletteSize + 2, // maxPaletteSize 1, 1, 8, @@ -256,7 +250,7 @@ const PixelFormatDescriptor Index2_MSB = { pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::MSBFirst>, BitOrder::MSBFirst, ByteOrder::Native, - 4, // maxPaletteSize + 4, // maxPaletteSize 2, 1, 4, @@ -278,7 +272,7 @@ const PixelFormatDescriptor Index2_LSB = { pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::LSBFirst>, BitOrder::LSBFirst, ByteOrder::Native, - 4, // maxPaletteSize + 4, // maxPaletteSize 2, 1, 4, @@ -300,7 +294,7 @@ const PixelFormatDescriptor Index4_MSB = { pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::MSBFirst>, BitOrder::MSBFirst, ByteOrder::Native, - 16, // maxPaletteSize + 16, // maxPaletteSize 4, 1, 2, @@ -322,7 +316,7 @@ const PixelFormatDescriptor Index4_LSB = { pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::LSBFirst>, BitOrder::LSBFirst, ByteOrder::Native, - 16, // maxPaletteSize + 16, // maxPaletteSize 4, 1, 2, @@ -332,10 +326,10 @@ const PixelFormatDescriptor Index4_LSB = { true, }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_INDEX_H +#endif // FLEXIMG_PIXEL_FORMAT_INDEX_H diff --git a/src/fleximg/image/pixel_format/rgb332.h b/src/fleximg/image/pixel_format/rgb332.h index 528d315..01a10bc 100644 --- a/src/fleximg/image/pixel_format/rgb332.h +++ b/src/fleximg/image/pixel_format/rgb332.h @@ -18,7 +18,7 @@ namespace PixelFormatIDs { inline const PixelFormatID RGB332 = &BuiltinFormats::RGB332; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -40,72 +40,63 @@ namespace FLEXIMG_NAMESPACE { namespace { // テーブル生成用マクロ: uint32_t値 = (255 << 24) | (B8 << 16) | (G8 << 8) | R8 -#define RGB332_ENTRY(p) \ - static_cast( \ - (static_cast((((p) >> 5) & 0x07) * 0x49 >> 1)) | \ - (static_cast((((p) >> 2) & 0x07) * 0x49 >> 1) << 8) | \ - (static_cast(((p) & 0x03) * 0x55) << 16) | \ - (static_cast(255) << 24)) - -#define RGB332_ROW(base) \ - RGB332_ENTRY(base + 0), RGB332_ENTRY(base + 1), RGB332_ENTRY(base + 2), \ - RGB332_ENTRY(base + 3), RGB332_ENTRY(base + 4), RGB332_ENTRY(base + 5), \ - RGB332_ENTRY(base + 6), RGB332_ENTRY(base + 7), RGB332_ENTRY(base + 8), \ - RGB332_ENTRY(base + 9), RGB332_ENTRY(base + 10), \ - RGB332_ENTRY(base + 11), RGB332_ENTRY(base + 12), \ - RGB332_ENTRY(base + 13), RGB332_ENTRY(base + 14), \ - RGB332_ENTRY(base + 15) +#define RGB332_ENTRY(p) \ + static_cast((static_cast((((p) >> 5) & 0x07) * 0x49 >> 1)) | \ + (static_cast((((p) >> 2) & 0x07) * 0x49 >> 1) << 8) | \ + (static_cast(((p) & 0x03) * 0x55) << 16) | (static_cast(255) << 24)) + +#define RGB332_ROW(base) \ + RGB332_ENTRY(base + 0), RGB332_ENTRY(base + 1), RGB332_ENTRY(base + 2), RGB332_ENTRY(base + 3), \ + RGB332_ENTRY(base + 4), RGB332_ENTRY(base + 5), RGB332_ENTRY(base + 6), RGB332_ENTRY(base + 7), \ + RGB332_ENTRY(base + 8), RGB332_ENTRY(base + 9), RGB332_ENTRY(base + 10), RGB332_ENTRY(base + 11), \ + RGB332_ENTRY(base + 12), RGB332_ENTRY(base + 13), RGB332_ENTRY(base + 14), RGB332_ENTRY(base + 15) // RGB332 → RGBA8 変換テーブル (256 × 4 = 1024 bytes) alignas(64) static const uint32_t rgb332ToRgba8[256] = { - RGB332_ROW(0x00), RGB332_ROW(0x10), RGB332_ROW(0x20), RGB332_ROW(0x30), - RGB332_ROW(0x40), RGB332_ROW(0x50), RGB332_ROW(0x60), RGB332_ROW(0x70), - RGB332_ROW(0x80), RGB332_ROW(0x90), RGB332_ROW(0xa0), RGB332_ROW(0xb0), + RGB332_ROW(0x00), RGB332_ROW(0x10), RGB332_ROW(0x20), RGB332_ROW(0x30), RGB332_ROW(0x40), RGB332_ROW(0x50), + RGB332_ROW(0x60), RGB332_ROW(0x70), RGB332_ROW(0x80), RGB332_ROW(0x90), RGB332_ROW(0xa0), RGB332_ROW(0xb0), RGB332_ROW(0xc0), RGB332_ROW(0xd0), RGB332_ROW(0xe0), RGB332_ROW(0xf0)}; #undef RGB332_ENTRY #undef RGB332_ROW -} // namespace +} // namespace -static void rgb332_toStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB332, ToStraight, pixelCount); - pixel_format::detail::lut8to32(static_cast(dst), - static_cast(src), pixelCount, - rgb332ToRgba8); +static void rgb332_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB332, ToStraight, pixelCount); + pixel_format::detail::lut8to32(static_cast(dst), static_cast(src), pixelCount, + rgb332ToRgba8); } // RGBA8 → RGB332 変換マクロ(32bitロードした値から変換) // (((r << 3) + g) << 2) + b の形式でESP32のシフト+加算命令を活用 -#define RGBA8_TO_RGB332(rgba) \ - (((((rgba) >> 5) << 3) + (((rgba) >> 13) & 0x07)) << 2) + \ - (((rgba) >> 22) & 0x03) - -static void rgb332_fromStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB332, FromStraight, pixelCount); - uint8_t *d = static_cast(dst); - const uint32_t *s = static_cast(src); - - // 端数処理(1ピクセル) - if (pixelCount & 1) { - auto rgba = *s++; - *d++ = static_cast(RGBA8_TO_RGB332(rgba)); - } - - // 2ピクセル単位でループ(ロードを先に発行してレイテンシ隠蔽) - pixelCount >>= 1; - while (pixelCount--) { - // 2つのロードを先に発行 - auto rgba0 = s[0]; - auto rgba1 = s[1]; - s += 2; - // 演算と8bitストア - d[0] = static_cast(RGBA8_TO_RGB332(rgba0)); - d[1] = static_cast(RGBA8_TO_RGB332(rgba1)); - d += 2; - } +#define RGBA8_TO_RGB332(rgba) (((((rgba) >> 5) << 3) + (((rgba) >> 13) & 0x07)) << 2) + (((rgba) >> 22) & 0x03) + +static void rgb332_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB332, FromStraight, pixelCount); + uint8_t *d = static_cast(dst); + const uint32_t *s = static_cast(src); + + // 端数処理(1ピクセル) + if (pixelCount & 1) { + auto rgba = *s++; + *d++ = static_cast(RGBA8_TO_RGB332(rgba)); + } + + // 2ピクセル単位でループ(ロードを先に発行してレイテンシ隠蔽) + pixelCount >>= 1; + while (pixelCount--) { + // 2つのロードを先に発行 + auto rgba0 = s[0]; + auto rgba1 = s[1]; + s += 2; + // 演算と8bitストア + d[0] = static_cast(RGBA8_TO_RGB332(rgba0)); + d[1] = static_cast(RGBA8_TO_RGB332(rgba1)); + d += 2; + } } #undef RGBA8_TO_RGB332 @@ -119,28 +110,28 @@ const PixelFormatDescriptor RGB332 = { "RGB332", rgb332_toStraight, rgb332_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - nullptr, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + nullptr, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_1Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_1Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_RGB332_H +#endif // FLEXIMG_PIXEL_FORMAT_RGB332_H diff --git a/src/fleximg/image/pixel_format/rgb565.h b/src/fleximg/image/pixel_format/rgb565.h index acc2c49..af77ace 100644 --- a/src/fleximg/image/pixel_format/rgb565.h +++ b/src/fleximg/image/pixel_format/rgb565.h @@ -13,14 +13,14 @@ namespace FLEXIMG_NAMESPACE { namespace BuiltinFormats { extern const PixelFormatDescriptor RGB565_LE; extern const PixelFormatDescriptor RGB565_BE; -} // namespace BuiltinFormats +} // namespace BuiltinFormats namespace PixelFormatIDs { inline const PixelFormatID RGB565_LE = &BuiltinFormats::RGB565_LE; inline const PixelFormatID RGB565_BE = &BuiltinFormats::RGB565_BE; -} // namespace PixelFormatIDs +} // namespace PixelFormatIDs -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -57,148 +57,132 @@ namespace FLEXIMG_NAMESPACE { namespace { // 上位バイト用エントリ: uint16_t値 = (G_high << 8) | R8 -#define RGB565_HIGH_ENTRY(h) \ - static_cast((((((h) & 0x07) << 5) | (((h) & 0x07) >> 1)) << 8) | \ - ((((h) >> 3) << 3) | (((h) >> 3) >> 2))) +#define RGB565_HIGH_ENTRY(h) \ + static_cast((((((h) & 0x07) << 5) | (((h) & 0x07) >> 1)) << 8) | ((((h) >> 3) << 3) | (((h) >> 3) >> 2))) // 下位バイト用エントリ: uint16_t値 = (G_low << 8) | B8 -#define RGB565_LOW_ENTRY(l) \ - static_cast((((((l) >> 5) & 0x07) << 2) << 8) | \ - ((((l) & 0x1F) << 3) | (((l) & 0x1F) >> 2))) - -#define RGB565_HIGH_ROW(base) \ - RGB565_HIGH_ENTRY(base + 0), RGB565_HIGH_ENTRY(base + 1), \ - RGB565_HIGH_ENTRY(base + 2), RGB565_HIGH_ENTRY(base + 3), \ - RGB565_HIGH_ENTRY(base + 4), RGB565_HIGH_ENTRY(base + 5), \ - RGB565_HIGH_ENTRY(base + 6), RGB565_HIGH_ENTRY(base + 7), \ - RGB565_HIGH_ENTRY(base + 8), RGB565_HIGH_ENTRY(base + 9), \ - RGB565_HIGH_ENTRY(base + 10), RGB565_HIGH_ENTRY(base + 11), \ - RGB565_HIGH_ENTRY(base + 12), RGB565_HIGH_ENTRY(base + 13), \ - RGB565_HIGH_ENTRY(base + 14), RGB565_HIGH_ENTRY(base + 15) - -#define RGB565_LOW_ROW(base) \ - RGB565_LOW_ENTRY(base + 0), RGB565_LOW_ENTRY(base + 1), \ - RGB565_LOW_ENTRY(base + 2), RGB565_LOW_ENTRY(base + 3), \ - RGB565_LOW_ENTRY(base + 4), RGB565_LOW_ENTRY(base + 5), \ - RGB565_LOW_ENTRY(base + 6), RGB565_LOW_ENTRY(base + 7), \ - RGB565_LOW_ENTRY(base + 8), RGB565_LOW_ENTRY(base + 9), \ - RGB565_LOW_ENTRY(base + 10), RGB565_LOW_ENTRY(base + 11), \ - RGB565_LOW_ENTRY(base + 12), RGB565_LOW_ENTRY(base + 13), \ - RGB565_LOW_ENTRY(base + 14), RGB565_LOW_ENTRY(base + 15) +#define RGB565_LOW_ENTRY(l) \ + static_cast((((((l) >> 5) & 0x07) << 2) << 8) | ((((l) & 0x1F) << 3) | (((l) & 0x1F) >> 2))) + +#define RGB565_HIGH_ROW(base) \ + RGB565_HIGH_ENTRY(base + 0), RGB565_HIGH_ENTRY(base + 1), RGB565_HIGH_ENTRY(base + 2), \ + RGB565_HIGH_ENTRY(base + 3), RGB565_HIGH_ENTRY(base + 4), RGB565_HIGH_ENTRY(base + 5), \ + RGB565_HIGH_ENTRY(base + 6), RGB565_HIGH_ENTRY(base + 7), RGB565_HIGH_ENTRY(base + 8), \ + RGB565_HIGH_ENTRY(base + 9), RGB565_HIGH_ENTRY(base + 10), RGB565_HIGH_ENTRY(base + 11), \ + RGB565_HIGH_ENTRY(base + 12), RGB565_HIGH_ENTRY(base + 13), RGB565_HIGH_ENTRY(base + 14), \ + RGB565_HIGH_ENTRY(base + 15) + +#define RGB565_LOW_ROW(base) \ + RGB565_LOW_ENTRY(base + 0), RGB565_LOW_ENTRY(base + 1), RGB565_LOW_ENTRY(base + 2), RGB565_LOW_ENTRY(base + 3), \ + RGB565_LOW_ENTRY(base + 4), RGB565_LOW_ENTRY(base + 5), RGB565_LOW_ENTRY(base + 6), \ + RGB565_LOW_ENTRY(base + 7), RGB565_LOW_ENTRY(base + 8), RGB565_LOW_ENTRY(base + 9), \ + RGB565_LOW_ENTRY(base + 10), RGB565_LOW_ENTRY(base + 11), RGB565_LOW_ENTRY(base + 12), \ + RGB565_LOW_ENTRY(base + 13), RGB565_LOW_ENTRY(base + 14), RGB565_LOW_ENTRY(base + 15) // RGB565上位バイト用テーブル (256 × 2 = 512 bytes): (G_high << 8) | R8 alignas(64) static const uint16_t rgb565HighTable[256] = { - RGB565_HIGH_ROW(0x00), RGB565_HIGH_ROW(0x10), RGB565_HIGH_ROW(0x20), - RGB565_HIGH_ROW(0x30), RGB565_HIGH_ROW(0x40), RGB565_HIGH_ROW(0x50), - RGB565_HIGH_ROW(0x60), RGB565_HIGH_ROW(0x70), RGB565_HIGH_ROW(0x80), - RGB565_HIGH_ROW(0x90), RGB565_HIGH_ROW(0xa0), RGB565_HIGH_ROW(0xb0), - RGB565_HIGH_ROW(0xc0), RGB565_HIGH_ROW(0xd0), RGB565_HIGH_ROW(0xe0), - RGB565_HIGH_ROW(0xf0)}; + RGB565_HIGH_ROW(0x00), RGB565_HIGH_ROW(0x10), RGB565_HIGH_ROW(0x20), RGB565_HIGH_ROW(0x30), + RGB565_HIGH_ROW(0x40), RGB565_HIGH_ROW(0x50), RGB565_HIGH_ROW(0x60), RGB565_HIGH_ROW(0x70), + RGB565_HIGH_ROW(0x80), RGB565_HIGH_ROW(0x90), RGB565_HIGH_ROW(0xa0), RGB565_HIGH_ROW(0xb0), + RGB565_HIGH_ROW(0xc0), RGB565_HIGH_ROW(0xd0), RGB565_HIGH_ROW(0xe0), RGB565_HIGH_ROW(0xf0)}; // RGB565下位バイト用テーブル (256 × 2 = 512 bytes): (G_low << 8) | B8 alignas(64) static const uint16_t rgb565LowTable[256] = { - RGB565_LOW_ROW(0x00), RGB565_LOW_ROW(0x10), RGB565_LOW_ROW(0x20), - RGB565_LOW_ROW(0x30), RGB565_LOW_ROW(0x40), RGB565_LOW_ROW(0x50), - RGB565_LOW_ROW(0x60), RGB565_LOW_ROW(0x70), RGB565_LOW_ROW(0x80), - RGB565_LOW_ROW(0x90), RGB565_LOW_ROW(0xa0), RGB565_LOW_ROW(0xb0), - RGB565_LOW_ROW(0xc0), RGB565_LOW_ROW(0xd0), RGB565_LOW_ROW(0xe0), - RGB565_LOW_ROW(0xf0)}; + RGB565_LOW_ROW(0x00), RGB565_LOW_ROW(0x10), RGB565_LOW_ROW(0x20), RGB565_LOW_ROW(0x30), + RGB565_LOW_ROW(0x40), RGB565_LOW_ROW(0x50), RGB565_LOW_ROW(0x60), RGB565_LOW_ROW(0x70), + RGB565_LOW_ROW(0x80), RGB565_LOW_ROW(0x90), RGB565_LOW_ROW(0xa0), RGB565_LOW_ROW(0xb0), + RGB565_LOW_ROW(0xc0), RGB565_LOW_ROW(0xd0), RGB565_LOW_ROW(0xe0), RGB565_LOW_ROW(0xf0)}; #undef RGB565_HIGH_ENTRY #undef RGB565_LOW_ENTRY #undef RGB565_HIGH_ROW #undef RGB565_LOW_ROW -} // namespace +} // namespace // RGB565_LE→RGBA8_Straight 1ピクセル変換マクロ(ルックアップテーブル使用) // s: uint8_t*, d: uint8_t*, s_off: srcオフセット, d_off: dstオフセット // RGB565_LE: [low_byte, high_byte] in memory -#define RGB565LE_TO_STRAIGHT_PIXEL(s_off, d_off) \ - do { \ - auto l16 = rgb565LowTable[s[s_off]]; \ - auto h16 = rgb565HighTable[s[s_off + 1]]; \ - d[d_off + 3] = 255; \ - d[d_off + 2] = static_cast(l16); \ - *reinterpret_cast(&d[d_off]) = \ - static_cast(h16 + (l16 & 0xFF00)); \ - } while (0) - -#define RGB565LE_TO_STRAIGHT_PIXEL_x2(s_off, d_off) \ - do { \ - auto s0 = s[s_off + 0]; \ - auto s1 = s[s_off + 1]; \ - auto s2 = s[s_off + 2]; \ - auto s3 = s[s_off + 3]; \ - auto l16_0 = rgb565LowTable[s0]; \ - auto h16_0 = rgb565HighTable[s1]; \ - auto l16_1 = rgb565LowTable[s2]; \ - auto h16_1 = rgb565HighTable[s3]; \ - *reinterpret_cast(&d[d_off]) = \ - static_cast(h16_0 + (l16_0 & 0xFF00)); \ - d[d_off + 2] = static_cast(l16_0); \ - d[d_off + 3] = 255; \ - *reinterpret_cast(&d[d_off + 4]) = \ - static_cast(h16_1 + (l16_1 & 0xFF00)); \ - d[d_off + 6] = static_cast(l16_1); \ - d[d_off + 7] = 255; \ - } while (0) - -static void rgb565le_toStraight(void *__restrict__ dst, - const void *__restrict__ src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB565_LE, ToStraight, pixelCount); - const uint8_t *__restrict__ s = static_cast(src); - uint8_t *__restrict__ d = static_cast(dst); - - // 端数処理(1ピクセル) - if (pixelCount & 1) { - RGB565LE_TO_STRAIGHT_PIXEL(0, 0); - s += 2; - d += 4; - } - - // 2ピクセル単位でループ - pixelCount >>= 1; - while (pixelCount--) { - RGB565LE_TO_STRAIGHT_PIXEL_x2(0, 0); - s += 4; - d += 8; - } +#define RGB565LE_TO_STRAIGHT_PIXEL(s_off, d_off) \ + do { \ + auto l16 = rgb565LowTable[s[s_off]]; \ + auto h16 = rgb565HighTable[s[s_off + 1]]; \ + d[d_off + 3] = 255; \ + d[d_off + 2] = static_cast(l16); \ + *reinterpret_cast(&d[d_off]) = static_cast(h16 + (l16 & 0xFF00)); \ + } while (0) + +#define RGB565LE_TO_STRAIGHT_PIXEL_x2(s_off, d_off) \ + do { \ + auto s0 = s[s_off + 0]; \ + auto s1 = s[s_off + 1]; \ + auto s2 = s[s_off + 2]; \ + auto s3 = s[s_off + 3]; \ + auto l16_0 = rgb565LowTable[s0]; \ + auto h16_0 = rgb565HighTable[s1]; \ + auto l16_1 = rgb565LowTable[s2]; \ + auto h16_1 = rgb565HighTable[s3]; \ + *reinterpret_cast(&d[d_off]) = static_cast(h16_0 + (l16_0 & 0xFF00)); \ + d[d_off + 2] = static_cast(l16_0); \ + d[d_off + 3] = 255; \ + *reinterpret_cast(&d[d_off + 4]) = static_cast(h16_1 + (l16_1 & 0xFF00)); \ + d[d_off + 6] = static_cast(l16_1); \ + d[d_off + 7] = 255; \ + } while (0) + +static void rgb565le_toStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB565_LE, ToStraight, pixelCount); + const uint8_t *__restrict__ s = static_cast(src); + uint8_t *__restrict__ d = static_cast(dst); + + // 端数処理(1ピクセル) + if (pixelCount & 1) { + RGB565LE_TO_STRAIGHT_PIXEL(0, 0); + s += 2; + d += 4; + } + + // 2ピクセル単位でループ + pixelCount >>= 1; + while (pixelCount--) { + RGB565LE_TO_STRAIGHT_PIXEL_x2(0, 0); + s += 4; + d += 8; + } } #undef RGB565LE_TO_STRAIGHT_PIXEL #undef RGB565LE_TO_STRAIGHT_PIXEL_x2 // RGBA8 → RGB565 変換マクロ(32bitロードした値から変換) -#define RGBA8_TO_RGB565_LE(rgba) \ - (((((rgba) >> 3) << 6) + (((rgba) >> 10) & 0x3F)) << 5) + \ - (((rgba) >> 19) & 0x1F) - -static void rgb565le_fromStraight(void *__restrict__ dst, - const void *__restrict__ src, - size_t pixelCount, const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB565_LE, FromStraight, pixelCount); - uint16_t *__restrict__ d = static_cast(dst); - const uint32_t *__restrict__ s = static_cast(src); - - // 端数処理(1ピクセル) - if (pixelCount & 1) { - auto rgba0 = s[0]; - s++; - d[0] = static_cast(RGBA8_TO_RGB565_LE(rgba0)); - d++; - } - - // 2ピクセル単位でループ - pixelCount >>= 1; - while (pixelCount--) { - auto rgba0 = s[0]; - auto rgba1 = s[1]; - s += 2; - d[0] = static_cast(RGBA8_TO_RGB565_LE(rgba0)); - d[1] = static_cast(RGBA8_TO_RGB565_LE(rgba1)); - d += 2; - } +#define RGBA8_TO_RGB565_LE(rgba) (((((rgba) >> 3) << 6) + (((rgba) >> 10) & 0x3F)) << 5) + (((rgba) >> 19) & 0x1F) + +static void rgb565le_fromStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB565_LE, FromStraight, pixelCount); + uint16_t *__restrict__ d = static_cast(dst); + const uint32_t *__restrict__ s = static_cast(src); + + // 端数処理(1ピクセル) + if (pixelCount & 1) { + auto rgba0 = s[0]; + s++; + d[0] = static_cast(RGBA8_TO_RGB565_LE(rgba0)); + d++; + } + + // 2ピクセル単位でループ + pixelCount >>= 1; + while (pixelCount--) { + auto rgba0 = s[0]; + auto rgba1 = s[1]; + s += 2; + d[0] = static_cast(RGBA8_TO_RGB565_LE(rgba0)); + d[1] = static_cast(RGBA8_TO_RGB565_LE(rgba1)); + d += 2; + } } // ======================================================================== @@ -208,104 +192,101 @@ static void rgb565le_fromStraight(void *__restrict__ dst, // RGB565_BE→RGBA8_Straight 1ピクセル変換マクロ(ルックアップテーブル使用) // s: uint8_t*, d: uint8_t*, s_off: srcオフセット, d_off: dstオフセット // RGB565_BE: [high_byte, low_byte] in memory(LEとは逆) -#define RGB565BE_TO_STRAIGHT_PIXEL(s_off, d_off) \ - do { \ - auto l16 = rgb565LowTable[s[s_off + 1]]; \ - auto h16 = rgb565HighTable[s[s_off]]; \ - d[d_off + 3] = 255; \ - d[d_off + 2] = static_cast(l16); \ - *reinterpret_cast(&d[d_off]) = \ - static_cast(h16 + (l16 & 0xFF00)); \ - } while (0) - -#define RGB565BE_TO_STRAIGHT_PIXEL_x2(s_off, d_off) \ - do { \ - auto s0 = s[s_off + 0]; \ - auto s1 = s[s_off + 1]; \ - auto s2 = s[s_off + 2]; \ - auto s3 = s[s_off + 3]; \ - auto h16_0 = rgb565HighTable[s0]; \ - auto l16_0 = rgb565LowTable[s1]; \ - auto h16_1 = rgb565HighTable[s2]; \ - auto l16_1 = rgb565LowTable[s3]; \ - *reinterpret_cast(&d[d_off]) = \ - static_cast(h16_0 + (l16_0 & 0xFF00)); \ - d[d_off + 2] = static_cast(l16_0); \ - d[d_off + 3] = 255; \ - *reinterpret_cast(&d[d_off + 4]) = \ - static_cast(h16_1 + (l16_1 & 0xFF00)); \ - d[d_off + 6] = static_cast(l16_1); \ - d[d_off + 7] = 255; \ - } while (0) - -static void rgb565be_toStraight(void *__restrict__ dst, - const void *__restrict__ src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB565_BE, ToStraight, pixelCount); - const uint8_t *__restrict__ s = static_cast(src); - uint8_t *__restrict__ d = static_cast(dst); - - // 端数処理(1ピクセル) - if (pixelCount & 1) { - RGB565BE_TO_STRAIGHT_PIXEL(0, 0); - s += 2; - d += 4; - } - - // 2ピクセル単位でループ - pixelCount >>= 1; - while (pixelCount--) { - RGB565BE_TO_STRAIGHT_PIXEL_x2(0, 0); - s += 4; - d += 8; - } +#define RGB565BE_TO_STRAIGHT_PIXEL(s_off, d_off) \ + do { \ + auto l16 = rgb565LowTable[s[s_off + 1]]; \ + auto h16 = rgb565HighTable[s[s_off]]; \ + d[d_off + 3] = 255; \ + d[d_off + 2] = static_cast(l16); \ + *reinterpret_cast(&d[d_off]) = static_cast(h16 + (l16 & 0xFF00)); \ + } while (0) + +#define RGB565BE_TO_STRAIGHT_PIXEL_x2(s_off, d_off) \ + do { \ + auto s0 = s[s_off + 0]; \ + auto s1 = s[s_off + 1]; \ + auto s2 = s[s_off + 2]; \ + auto s3 = s[s_off + 3]; \ + auto h16_0 = rgb565HighTable[s0]; \ + auto l16_0 = rgb565LowTable[s1]; \ + auto h16_1 = rgb565HighTable[s2]; \ + auto l16_1 = rgb565LowTable[s3]; \ + *reinterpret_cast(&d[d_off]) = static_cast(h16_0 + (l16_0 & 0xFF00)); \ + d[d_off + 2] = static_cast(l16_0); \ + d[d_off + 3] = 255; \ + *reinterpret_cast(&d[d_off + 4]) = static_cast(h16_1 + (l16_1 & 0xFF00)); \ + d[d_off + 6] = static_cast(l16_1); \ + d[d_off + 7] = 255; \ + } while (0) + +static void rgb565be_toStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB565_BE, ToStraight, pixelCount); + const uint8_t *__restrict__ s = static_cast(src); + uint8_t *__restrict__ d = static_cast(dst); + + // 端数処理(1ピクセル) + if (pixelCount & 1) { + RGB565BE_TO_STRAIGHT_PIXEL(0, 0); + s += 2; + d += 4; + } + + // 2ピクセル単位でループ + pixelCount >>= 1; + while (pixelCount--) { + RGB565BE_TO_STRAIGHT_PIXEL_x2(0, 0); + s += 4; + d += 8; + } } #undef RGB565BE_TO_STRAIGHT_PIXEL #undef RGB565BE_TO_STRAIGHT_PIXEL_x2 -static void rgb565be_fromStraight(void *__restrict__ dst, - const void *__restrict__ src, - size_t pixelCount, const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB565_BE, FromStraight, pixelCount); - uint8_t *__restrict__ d = static_cast(dst); - const uint32_t *__restrict__ s = static_cast(src); - - // 端数処理(1ピクセル) - if (pixelCount & 1) { - auto rgba0 = s[0]; - s++; - d[0] = static_cast((rgba0 & 0xF8) + ((rgba0 >> 13) & 0x07)); - d[1] = static_cast(((rgba0 >> 10) << 5) + ((rgba0 >> 19) & 0x1F)); - d += 2; - } - - // 2ピクセル単位でループ - pixelCount >>= 1; - while (pixelCount--) { - auto rgba0 = s[0]; - auto rgba1 = s[1]; - s += 2; - - d[0] = static_cast((rgba0 & 0xF8) + ((rgba0 >> 13) & 0x07)); - d[1] = static_cast(((rgba0 >> 10) << 5) + ((rgba0 >> 19) & 0x1F)); - d[2] = static_cast((rgba1 & 0xF8) + ((rgba1 >> 13) & 0x07)); - d[3] = static_cast(((rgba1 >> 10) << 5) + ((rgba1 >> 19) & 0x1F)); - d += 4; - } +static void rgb565be_fromStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB565_BE, FromStraight, pixelCount); + uint8_t *__restrict__ d = static_cast(dst); + const uint32_t *__restrict__ s = static_cast(src); + + // 端数処理(1ピクセル) + if (pixelCount & 1) { + auto rgba0 = s[0]; + s++; + d[0] = static_cast((rgba0 & 0xF8) + ((rgba0 >> 13) & 0x07)); + d[1] = static_cast(((rgba0 >> 10) << 5) + ((rgba0 >> 19) & 0x1F)); + d += 2; + } + + // 2ピクセル単位でループ + pixelCount >>= 1; + while (pixelCount--) { + auto rgba0 = s[0]; + auto rgba1 = s[1]; + s += 2; + + d[0] = static_cast((rgba0 & 0xF8) + ((rgba0 >> 13) & 0x07)); + d[1] = static_cast(((rgba0 >> 10) << 5) + ((rgba0 >> 19) & 0x1F)); + d[2] = static_cast((rgba1 & 0xF8) + ((rgba1 >> 13) & 0x07)); + d[3] = static_cast(((rgba1 >> 10) << 5) + ((rgba1 >> 19) & 0x1F)); + d += 4; + } } // ======================================================================== // 16bit用バイトスワップ(RGB565_LE ↔ RGB565_BE) // ======================================================================== -static void swap16(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - const uint16_t *srcPtr = static_cast(src); - uint16_t *dstPtr = static_cast(dst); - for (size_t i = 0; i < pixelCount; ++i) { - uint16_t v = srcPtr[i]; - dstPtr[i] = static_cast((v >> 8) | (v << 8)); - } +static void swap16(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + const uint16_t *srcPtr = static_cast(src); + uint16_t *dstPtr = static_cast(dst); + for (size_t i = 0; i < pixelCount; ++i) { + uint16_t v = srcPtr[i]; + dstPtr[i] = static_cast((v >> 8) | (v << 8)); + } } // ------------------------------------------------------------------------ @@ -321,50 +302,50 @@ const PixelFormatDescriptor RGB565_LE = { "RGB565_LE", rgb565le_toStraight, rgb565le_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - &RGB565_BE, // siblingEndian - swap16, // swapEndian - pixel_format::detail::copyRowDDA_2Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_2Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + &RGB565_BE, // siblingEndian + swap16, // swapEndian + pixel_format::detail::copyRowDDA_2Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_2Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::LittleEndian, - 0, // maxPaletteSize - 16, // bitsPerPixel - 2, // bytesPerPixel - 1, // pixelsPerUnit - 2, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 16, // bitsPerPixel + 2, // bytesPerPixel + 1, // pixelsPerUnit + 2, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed }; const PixelFormatDescriptor RGB565_BE = { "RGB565_BE", rgb565be_toStraight, rgb565be_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - &RGB565_LE, // siblingEndian - swap16, // swapEndian - pixel_format::detail::copyRowDDA_2Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_2Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + &RGB565_LE, // siblingEndian + swap16, // swapEndian + pixel_format::detail::copyRowDDA_2Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_2Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::BigEndian, - 0, // maxPaletteSize - 16, // bitsPerPixel - 2, // bytesPerPixel - 1, // pixelsPerUnit - 2, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 16, // bitsPerPixel + 2, // bytesPerPixel + 1, // pixelsPerUnit + 2, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_RGB565_H +#endif // FLEXIMG_PIXEL_FORMAT_RGB565_H diff --git a/src/fleximg/image/pixel_format/rgb888.h b/src/fleximg/image/pixel_format/rgb888.h index 84a15dd..298d17f 100644 --- a/src/fleximg/image/pixel_format/rgb888.h +++ b/src/fleximg/image/pixel_format/rgb888.h @@ -13,14 +13,14 @@ namespace FLEXIMG_NAMESPACE { namespace BuiltinFormats { extern const PixelFormatDescriptor RGB888; extern const PixelFormatDescriptor BGR888; -} // namespace BuiltinFormats +} // namespace BuiltinFormats namespace PixelFormatIDs { inline const PixelFormatID RGB888 = &BuiltinFormats::RGB888; inline const PixelFormatID BGR888 = &BuiltinFormats::BGR888; -} // namespace PixelFormatIDs +} // namespace PixelFormatIDs -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -35,162 +35,162 @@ namespace FLEXIMG_NAMESPACE { // RGB888: 24bit RGB (mem[0]=R, mem[1]=G, mem[2]=B) // ======================================================================== -static void rgb888_toStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB888, ToStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - - // 端数処理(1〜3ピクセル) - size_t remainder = pixelCount & 3; - while (remainder--) { - d[0] = s[0]; // R - d[1] = s[1]; // G - d[2] = s[2]; // B - d[3] = 255; // A - s += 3; - d += 4; - } - - // 4ピクセル単位でループ - pixelCount >>= 2; - while (pixelCount--) { - d[0] = s[0]; - d[1] = s[1]; - d[2] = s[2]; - d[3] = 255; - d[4] = s[3]; - d[5] = s[4]; - d[6] = s[5]; - d[7] = 255; - d[8] = s[6]; - d[9] = s[7]; - d[10] = s[8]; - d[11] = 255; - d[12] = s[9]; - d[13] = s[10]; - d[14] = s[11]; - d[15] = 255; - s += 12; - d += 16; - } +static void rgb888_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB888, ToStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + + // 端数処理(1〜3ピクセル) + size_t remainder = pixelCount & 3; + while (remainder--) { + d[0] = s[0]; // R + d[1] = s[1]; // G + d[2] = s[2]; // B + d[3] = 255; // A + s += 3; + d += 4; + } + + // 4ピクセル単位でループ + pixelCount >>= 2; + while (pixelCount--) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = 255; + d[4] = s[3]; + d[5] = s[4]; + d[6] = s[5]; + d[7] = 255; + d[8] = s[6]; + d[9] = s[7]; + d[10] = s[8]; + d[11] = 255; + d[12] = s[9]; + d[13] = s[10]; + d[14] = s[11]; + d[15] = 255; + s += 12; + d += 16; + } } -static void rgb888_fromStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGB888, FromStraight, pixelCount); - uint8_t *d = static_cast(dst); - const uint8_t *s = static_cast(src); - - // 端数処理(1〜3ピクセル) - size_t remainder = pixelCount & 3; - while (remainder--) { - d[0] = s[0]; // R - d[1] = s[1]; // G - d[2] = s[2]; // B - s += 4; - d += 3; - } - - // 4ピクセル単位でループ - pixelCount >>= 2; - while (pixelCount--) { - d[0] = s[0]; - d[1] = s[1]; - d[2] = s[2]; - d[3] = s[4]; - d[4] = s[5]; - d[5] = s[6]; - d[6] = s[8]; - d[7] = s[9]; - d[8] = s[10]; - d[9] = s[12]; - d[10] = s[13]; - d[11] = s[14]; - s += 16; - d += 12; - } +static void rgb888_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGB888, FromStraight, pixelCount); + uint8_t *d = static_cast(dst); + const uint8_t *s = static_cast(src); + + // 端数処理(1〜3ピクセル) + size_t remainder = pixelCount & 3; + while (remainder--) { + d[0] = s[0]; // R + d[1] = s[1]; // G + d[2] = s[2]; // B + s += 4; + d += 3; + } + + // 4ピクセル単位でループ + pixelCount >>= 2; + while (pixelCount--) { + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[4]; + d[4] = s[5]; + d[5] = s[6]; + d[6] = s[8]; + d[7] = s[9]; + d[8] = s[10]; + d[9] = s[12]; + d[10] = s[13]; + d[11] = s[14]; + s += 16; + d += 12; + } } // ======================================================================== // BGR888: 24bit BGR (mem[0]=B, mem[1]=G, mem[2]=R) // ======================================================================== -static void bgr888_toStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(BGR888, ToStraight, pixelCount); - const uint8_t *s = static_cast(src); - uint8_t *d = static_cast(dst); - - // 端数処理(1〜3ピクセル) - size_t remainder = pixelCount & 3; - while (remainder--) { - d[0] = s[2]; // R (src の B 位置) - d[1] = s[1]; // G - d[2] = s[0]; // B (src の R 位置) - d[3] = 255; - s += 3; - d += 4; - } - - // 4ピクセル単位でループ - pixelCount >>= 2; - while (pixelCount--) { - d[0] = s[2]; - d[1] = s[1]; - d[2] = s[0]; - d[3] = 255; - d[4] = s[5]; - d[5] = s[4]; - d[6] = s[3]; - d[7] = 255; - d[8] = s[8]; - d[9] = s[7]; - d[10] = s[6]; - d[11] = 255; - d[12] = s[11]; - d[13] = s[10]; - d[14] = s[9]; - d[15] = 255; - s += 12; - d += 16; - } +static void bgr888_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(BGR888, ToStraight, pixelCount); + const uint8_t *s = static_cast(src); + uint8_t *d = static_cast(dst); + + // 端数処理(1〜3ピクセル) + size_t remainder = pixelCount & 3; + while (remainder--) { + d[0] = s[2]; // R (src の B 位置) + d[1] = s[1]; // G + d[2] = s[0]; // B (src の R 位置) + d[3] = 255; + s += 3; + d += 4; + } + + // 4ピクセル単位でループ + pixelCount >>= 2; + while (pixelCount--) { + d[0] = s[2]; + d[1] = s[1]; + d[2] = s[0]; + d[3] = 255; + d[4] = s[5]; + d[5] = s[4]; + d[6] = s[3]; + d[7] = 255; + d[8] = s[8]; + d[9] = s[7]; + d[10] = s[6]; + d[11] = 255; + d[12] = s[11]; + d[13] = s[10]; + d[14] = s[9]; + d[15] = 255; + s += 12; + d += 16; + } } -static void bgr888_fromStraight(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(BGR888, FromStraight, pixelCount); - uint8_t *d = static_cast(dst); - const uint8_t *s = static_cast(src); - - // 端数処理(1〜3ピクセル) - size_t remainder = pixelCount & 3; - while (remainder--) { - d[0] = s[2]; // B - d[1] = s[1]; // G - d[2] = s[0]; // R - s += 4; - d += 3; - } - - // 4ピクセル単位でループ - pixelCount >>= 2; - while (pixelCount--) { - d[0] = s[2]; - d[1] = s[1]; - d[2] = s[0]; - d[3] = s[6]; - d[4] = s[5]; - d[5] = s[4]; - d[6] = s[10]; - d[7] = s[9]; - d[8] = s[8]; - d[9] = s[14]; - d[10] = s[13]; - d[11] = s[12]; - s += 16; - d += 12; - } +static void bgr888_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(BGR888, FromStraight, pixelCount); + uint8_t *d = static_cast(dst); + const uint8_t *s = static_cast(src); + + // 端数処理(1〜3ピクセル) + size_t remainder = pixelCount & 3; + while (remainder--) { + d[0] = s[2]; // B + d[1] = s[1]; // G + d[2] = s[0]; // R + s += 4; + d += 3; + } + + // 4ピクセル単位でループ + pixelCount >>= 2; + while (pixelCount--) { + d[0] = s[2]; + d[1] = s[1]; + d[2] = s[0]; + d[3] = s[6]; + d[4] = s[5]; + d[5] = s[4]; + d[6] = s[10]; + d[7] = s[9]; + d[8] = s[8]; + d[9] = s[14]; + d[10] = s[13]; + d[11] = s[12]; + s += 16; + d += 12; + } } // ======================================================================== @@ -198,16 +198,16 @@ static void bgr888_fromStraight(void *dst, const void *src, size_t pixelCount, // ======================================================================== // 24bit用チャンネルスワップ(RGB888 ↔ BGR888) -static void swap24(void *dst, const void *src, size_t pixelCount, - const PixelAuxInfo *) { - const uint8_t *srcPtr = static_cast(src); - uint8_t *dstPtr = static_cast(dst); - for (size_t i = 0; i < pixelCount; ++i) { - size_t idx = i * 3; - dstPtr[idx + 0] = srcPtr[idx + 2]; - dstPtr[idx + 1] = srcPtr[idx + 1]; - dstPtr[idx + 2] = srcPtr[idx + 0]; - } +static void swap24(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + const uint8_t *srcPtr = static_cast(src); + uint8_t *dstPtr = static_cast(dst); + for (size_t i = 0; i < pixelCount; ++i) { + size_t idx = i * 3; + dstPtr[idx + 0] = srcPtr[idx + 2]; + dstPtr[idx + 1] = srcPtr[idx + 1]; + dstPtr[idx + 2] = srcPtr[idx + 0]; + } } // ------------------------------------------------------------------------ @@ -223,50 +223,50 @@ const PixelFormatDescriptor RGB888 = { "RGB888", rgb888_toStraight, rgb888_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - &BGR888, // siblingEndian - swap24, // swapEndian - pixel_format::detail::copyRowDDA_3Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_3Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + &BGR888, // siblingEndian + swap24, // swapEndian + pixel_format::detail::copyRowDDA_3Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_3Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 24, // bitsPerPixel - 3, // bytesPerPixel - 1, // pixelsPerUnit - 3, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 24, // bitsPerPixel + 3, // bytesPerPixel + 1, // pixelsPerUnit + 3, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed }; const PixelFormatDescriptor BGR888 = { "BGR888", bgr888_toStraight, bgr888_fromStraight, - nullptr, // expandIndex - nullptr, // blendUnderStraight - &RGB888, // siblingEndian - swap24, // swapEndian - pixel_format::detail::copyRowDDA_3Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_3Byte, // copyQuadDDA + nullptr, // expandIndex + nullptr, // blendUnderStraight + &RGB888, // siblingEndian + swap24, // swapEndian + pixel_format::detail::copyRowDDA_3Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_3Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 24, // bitsPerPixel - 3, // bytesPerPixel - 1, // pixelsPerUnit - 3, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 24, // bitsPerPixel + 3, // bytesPerPixel + 1, // pixelsPerUnit + 3, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_RGB888_H +#endif // FLEXIMG_PIXEL_FORMAT_RGB888_H diff --git a/src/fleximg/image/pixel_format/rgba8_straight.h b/src/fleximg/image/pixel_format/rgba8_straight.h index a338785..9eed907 100644 --- a/src/fleximg/image/pixel_format/rgba8_straight.h +++ b/src/fleximg/image/pixel_format/rgba8_straight.h @@ -18,7 +18,7 @@ namespace PixelFormatIDs { inline const PixelFormatID RGBA8_Straight = &BuiltinFormats::RGBA8_Straight; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -35,17 +35,16 @@ namespace FLEXIMG_NAMESPACE { // ======================================================================== // RGBA8_Straight: Straight形式なのでコピー -static void rgba8Straight_toStraight(void *dst, const void *src, - size_t pixelCount, const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGBA8_Straight, ToStraight, pixelCount); - std::memcpy(dst, src, pixelCount * 4); +static void rgba8Straight_toStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGBA8_Straight, ToStraight, pixelCount); + std::memcpy(dst, src, pixelCount * 4); } -static void rgba8Straight_fromStraight(void *dst, const void *src, - size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGBA8_Straight, FromStraight, pixelCount); - std::memcpy(dst, src, pixelCount * 4); +static void rgba8Straight_fromStraight(void *dst, const void *src, size_t pixelCount, const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGBA8_Straight, FromStraight, pixelCount); + std::memcpy(dst, src, pixelCount * 4); } // blendUnderStraight: RGBA8_Straight形式のunder合成(背面への合成) @@ -69,222 +68,189 @@ static void rgba8Straight_fromStraight(void *dst, const void *src, // - dstW = (dstA * 255 * 256) / total, srcW = 256 - dstW // - 色計算: (d * dstW + s * srcW) >> 8 // - R,Bチャンネルを32ビット演算でまとめて処理 -static void rgba8Straight_blendUnderStraight(void *__restrict__ dst, - const void *__restrict__ src, - size_t pixelCount, - const PixelAuxInfo *) { - FLEXIMG_FMT_METRICS(RGBA8_Straight, BlendUnder, pixelCount); - if (pixelCount <= 0) - return; - - const uint8_t *__restrict__ s = static_cast(src); - uint_fast8_t srcA = s[3]; - uint8_t *__restrict__ d = static_cast(dst); - uint_fast8_t dstA = d[3]; - - // メインディスパッチ: srcAを優先的にチェック - if (srcA == 0) - goto handle_srcA_0; - if (dstA == 255) - goto handle_dstA_255; - if (dstA == 0) - goto handle_dstA_0; +static void rgba8Straight_blendUnderStraight(void *__restrict__ dst, const void *__restrict__ src, size_t pixelCount, + const PixelAuxInfo *) +{ + FLEXIMG_FMT_METRICS(RGBA8_Straight, BlendUnder, pixelCount); + if (pixelCount <= 0) return; + + const uint8_t *__restrict__ s = static_cast(src); + uint_fast8_t srcA = s[3]; + uint8_t *__restrict__ d = static_cast(dst); + uint_fast8_t dstA = d[3]; + + // メインディスパッチ: srcAを優先的にチェック + if (srcA == 0) goto handle_srcA_0; + if (dstA == 255) goto handle_dstA_255; + if (dstA == 0) goto handle_dstA_0; blend: - // ======================================================================== - // ブレンド処理ループ(srcA != 0, dstA != 0, dstA != 255) - // - // 正規化重み方式: - // total = dstA * 255 + srcA * (255 - dstA) // 合成後のアルファ×255 - // dstW = (dstA * 255 * 256) / total // dst側の重み(0〜256) - // srcW = 256 - dstW // src側の重み(0〜256) - // color = (d * dstW + s * srcW) >> 8 // シフトで除算を代替 - // - // 精度: 最大誤差±1(90%以上が完全一致) - // - // do-whileで末尾デクリメント: breakした時点でpixelCountはまだ減っていない - // ======================================================================== - do { - dstA = d[3]; - srcA = s[3]; - if (dstA == 255) - break; - if (dstA == 0) - break; - if (srcA == 0) - break; - - // 合成後アルファの計算(255スケール) - uint_fast32_t dstA_255 = dstA * 255; - uint_fast32_t invDstA = 255 - dstA; - uint_fast32_t srcA_invDstA = srcA * invDstA; - uint_fast32_t total = dstA_255 + srcA_invDstA; - - // 正規化重み(合計256、除算1回) - uint_fast32_t dstW = (dstA_255 * 256 + (total >> 1)) / total; - uint_fast32_t srcW = 256 - dstW; - - // R,Bを32ビットでまとめて処理(リトルエンディアン: [R,G,B,A]) - uint32_t d32 = *reinterpret_cast(d); - uint32_t s32 = *reinterpret_cast(s); - uint32_t d32_odd = d[1]; // G(単独処理) - uint32_t s32_odd = s[1]; - uint32_t d32_even = d32 & 0xFF00FF; // R, B(まとめて処理) - uint32_t s32_even = s32 & 0xFF00FF; - - // 重み付き加算 + シフト(255 * 256 = 65280 < 65536 でオーバーフローなし) - d32_even = d32_even * dstW + s32_even * srcW; - d32_odd = d32_odd * dstW + s32_odd * srcW; - - // 結果を書き込み - *reinterpret_cast(d) = d32_even >> 8; // R, B - d[1] = static_cast(d32_odd >> 8); // G - d[3] = static_cast((total + 127) / 255); // A(正確な計算) - + // ======================================================================== + // ブレンド処理ループ(srcA != 0, dstA != 0, dstA != 255) + // + // 正規化重み方式: + // total = dstA * 255 + srcA * (255 - dstA) // 合成後のアルファ×255 + // dstW = (dstA * 255 * 256) / total // dst側の重み(0〜256) + // srcW = 256 - dstW // src側の重み(0〜256) + // color = (d * dstW + s * srcW) >> 8 // シフトで除算を代替 + // + // 精度: 最大誤差±1(90%以上が完全一致) + // + // do-whileで末尾デクリメント: breakした時点でpixelCountはまだ減っていない + // ======================================================================== + do { + dstA = d[3]; + srcA = s[3]; + if (dstA == 255) break; + if (dstA == 0) break; + if (srcA == 0) break; + + // 合成後アルファの計算(255スケール) + uint_fast32_t dstA_255 = dstA * 255; + uint_fast32_t invDstA = 255 - dstA; + uint_fast32_t srcA_invDstA = srcA * invDstA; + uint_fast32_t total = dstA_255 + srcA_invDstA; + + // 正規化重み(合計256、除算1回) + uint_fast32_t dstW = (dstA_255 * 256 + (total >> 1)) / total; + uint_fast32_t srcW = 256 - dstW; + + // R,Bを32ビットでまとめて処理(リトルエンディアン: [R,G,B,A]) + uint32_t d32 = *reinterpret_cast(d); + uint32_t s32 = *reinterpret_cast(s); + uint32_t d32_odd = d[1]; // G(単独処理) + uint32_t s32_odd = s[1]; + uint32_t d32_even = d32 & 0xFF00FF; // R, B(まとめて処理) + uint32_t s32_even = s32 & 0xFF00FF; + + // 重み付き加算 + シフト(255 * 256 = 65280 < 65536 でオーバーフローなし) + d32_even = d32_even * dstW + s32_even * srcW; + d32_odd = d32_odd * dstW + s32_odd * srcW; + + // 結果を書き込み + *reinterpret_cast(d) = d32_even >> 8; // R, B + d[1] = static_cast(d32_odd >> 8); // G + d[3] = static_cast((total + 127) / 255); // A(正確な計算) + + d += 4; + s += 4; + } while (--pixelCount > 0); + if (pixelCount <= 0) return; + if (srcA == 0) goto handle_srcA_0; + if (dstA == 0) goto handle_dstA_0; + // ここには通常到達しない + + // ======================================================================== + // dst不透明(255) → 連続スキップ + // ======================================================================== +handle_dstA_255: { + // 現在のピクセルは255確認済み、スキップ + if (--pixelCount <= 0) return; d += 4; + dstA = d[3]; s += 4; - } while (--pixelCount > 0); - if (pixelCount <= 0) - return; - if (srcA == 0) - goto handle_srcA_0; - if (dstA == 0) - goto handle_dstA_0; - // ここには通常到達しない - - // ======================================================================== - // dst不透明(255) → 連続スキップ - // ======================================================================== -handle_dstA_255: { - // 現在のピクセルは255確認済み、スキップ - if (--pixelCount <= 0) - return; - d += 4; - dstA = d[3]; - s += 4; - - // 4ピクセル単位でスキップ - auto plimit = pixelCount >> 2; - if (plimit && dstA == 255) { - auto d_start = d; - do { - uint_fast8_t a1 = d[7]; - uint_fast8_t a2 = d[11]; - uint_fast8_t a3 = d[15]; - if ((dstA & a1 & a2 & a3) != 255) - break; - d += 16; - dstA = d[3]; - } while (--plimit); - auto pindex = (d - d_start); - if (d != d_start) { - pixelCount -= pindex >> 2; - if (pixelCount <= 0) - return; - s += pindex; + + // 4ピクセル単位でスキップ + auto plimit = pixelCount >> 2; + if (plimit && dstA == 255) { + auto d_start = d; + do { + uint_fast8_t a1 = d[7]; + uint_fast8_t a2 = d[11]; + uint_fast8_t a3 = d[15]; + if ((dstA & a1 & a2 & a3) != 255) break; + d += 16; + dstA = d[3]; + } while (--plimit); + auto pindex = (d - d_start); + if (d != d_start) { + pixelCount -= pindex >> 2; + if (pixelCount <= 0) return; + s += pindex; + } } - } - srcA = s[3]; - if (dstA == 255) - goto handle_dstA_255; - if (dstA == 0) - goto handle_dstA_0; - if (srcA == 0) - goto handle_srcA_0; - goto blend; + srcA = s[3]; + if (dstA == 255) goto handle_dstA_255; + if (dstA == 0) goto handle_dstA_0; + if (srcA == 0) goto handle_srcA_0; + goto blend; } - // ======================================================================== - // dst透明(0) → 連続コピー - // ======================================================================== + // ======================================================================== + // dst透明(0) → 連続コピー + // ======================================================================== handle_dstA_0: { - // 現在のピクセルは0確認済み、コピー - *reinterpret_cast(d) = *reinterpret_cast(s); - if (--pixelCount <= 0) - return; - d += 4; - dstA = d[3]; - s += 4; - - // 4ピクセル単位でコピー - auto plimit = pixelCount >> 2; - if (plimit && dstA == 0) { - auto s_start = s; - do { - uint_fast8_t a1 = d[7]; - uint_fast8_t a2 = d[11]; - uint_fast8_t a3 = d[15]; - if ((dstA | a1 | a2 | a3) != 0) - break; - reinterpret_cast(d)[0] = - reinterpret_cast(s)[0]; - reinterpret_cast(d)[1] = - reinterpret_cast(s)[1]; - reinterpret_cast(d)[2] = - reinterpret_cast(s)[2]; - reinterpret_cast(d)[3] = - reinterpret_cast(s)[3]; - d += 16; - dstA = d[3]; - s += 16; - } while (--plimit); - auto pindex = (s - s_start); - if (pindex) { - pixelCount -= pindex >> 2; - if (pixelCount <= 0) - return; + // 現在のピクセルは0確認済み、コピー + *reinterpret_cast(d) = *reinterpret_cast(s); + if (--pixelCount <= 0) return; + d += 4; + dstA = d[3]; + s += 4; + + // 4ピクセル単位でコピー + auto plimit = pixelCount >> 2; + if (plimit && dstA == 0) { + auto s_start = s; + do { + uint_fast8_t a1 = d[7]; + uint_fast8_t a2 = d[11]; + uint_fast8_t a3 = d[15]; + if ((dstA | a1 | a2 | a3) != 0) break; + reinterpret_cast(d)[0] = reinterpret_cast(s)[0]; + reinterpret_cast(d)[1] = reinterpret_cast(s)[1]; + reinterpret_cast(d)[2] = reinterpret_cast(s)[2]; + reinterpret_cast(d)[3] = reinterpret_cast(s)[3]; + d += 16; + dstA = d[3]; + s += 16; + } while (--plimit); + auto pindex = (s - s_start); + if (pindex) { + pixelCount -= pindex >> 2; + if (pixelCount <= 0) return; + } } - } - - srcA = s[3]; - if (dstA == 255) - goto handle_dstA_255; - if (dstA == 0) - goto handle_dstA_0; - if (srcA == 0) - goto handle_srcA_0; - goto blend; + + srcA = s[3]; + if (dstA == 255) goto handle_dstA_255; + if (dstA == 0) goto handle_dstA_0; + if (srcA == 0) goto handle_srcA_0; + goto blend; } - // ======================================================================== - // src透明(0) → 連続スキップ - // ======================================================================== + // ======================================================================== + // src透明(0) → 連続スキップ + // ======================================================================== handle_srcA_0: { - // 現在のピクセルは0確認済み、スキップ - if (--pixelCount <= 0) - return; - s += 4; - srcA = s[3]; - d += 4; - - // 4ピクセル単位でスキップ - auto plimit = pixelCount >> 2; - if (plimit && srcA == 0) { - auto s_start = s; - do { - uint_fast8_t a1 = s[7]; - uint_fast8_t a2 = s[11]; - uint_fast8_t a3 = s[15]; - if ((srcA | a1 | a2 | a3) != 0) - break; - s += 16; - srcA = s[3]; - } while (--plimit); - auto pindex = (s - s_start); - if (pindex) { - pixelCount -= pindex >> 2; - if (pixelCount <= 0) - return; - d += pindex; + // 現在のピクセルは0確認済み、スキップ + if (--pixelCount <= 0) return; + s += 4; + srcA = s[3]; + d += 4; + + // 4ピクセル単位でスキップ + auto plimit = pixelCount >> 2; + if (plimit && srcA == 0) { + auto s_start = s; + do { + uint_fast8_t a1 = s[7]; + uint_fast8_t a2 = s[11]; + uint_fast8_t a3 = s[15]; + if ((srcA | a1 | a2 | a3) != 0) break; + s += 16; + srcA = s[3]; + } while (--plimit); + auto pindex = (s - s_start); + if (pindex) { + pixelCount -= pindex >> 2; + if (pixelCount <= 0) return; + d += pindex; + } } - } - dstA = d[3]; - if (srcA == 0) - goto handle_srcA_0; - if (dstA == 255) - goto handle_dstA_255; - if (dstA == 0) - goto handle_dstA_0; - goto blend; + dstA = d[3]; + if (srcA == 0) goto handle_srcA_0; + if (dstA == 255) goto handle_dstA_255; + if (dstA == 0) goto handle_dstA_0; + goto blend; } } @@ -298,28 +264,28 @@ const PixelFormatDescriptor RGBA8_Straight = { "RGBA8_Straight", rgba8Straight_toStraight, rgba8Straight_fromStraight, - nullptr, // expandIndex - rgba8Straight_blendUnderStraight, // blendUnderStraight - nullptr, // siblingEndian - nullptr, // swapEndian - pixel_format::detail::copyRowDDA_4Byte, // copyRowDDA - pixel_format::detail::copyQuadDDA_4Byte, // copyQuadDDA + nullptr, // expandIndex + rgba8Straight_blendUnderStraight, // blendUnderStraight + nullptr, // siblingEndian + nullptr, // swapEndian + pixel_format::detail::copyRowDDA_4Byte, // copyRowDDA + pixel_format::detail::copyQuadDDA_4Byte, // copyQuadDDA BitOrder::MSBFirst, ByteOrder::Native, - 0, // maxPaletteSize - 32, // bitsPerPixel - 4, // bytesPerPixel - 1, // pixelsPerUnit - 4, // bytesPerUnit - 4, // channelCount - true, // hasAlpha - false, // isIndexed + 0, // maxPaletteSize + 32, // bitsPerPixel + 4, // bytesPerPixel + 1, // pixelsPerUnit + 4, // bytesPerUnit + 4, // channelCount + true, // hasAlpha + false, // isIndexed }; -} // namespace BuiltinFormats +} // namespace BuiltinFormats -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_PIXEL_FORMAT_RGBA8_STRAIGHT_H +#endif // FLEXIMG_PIXEL_FORMAT_RGBA8_STRAIGHT_H diff --git a/src/fleximg/image/render_types.h b/src/fleximg/image/render_types.h index 9152d2a..459e57e 100644 --- a/src/fleximg/image/render_types.h +++ b/src/fleximg/image/render_types.h @@ -18,7 +18,7 @@ namespace core { class RenderContext; } using core::RenderContext; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE #include "image_buffer_entry_pool.h" @@ -36,15 +36,15 @@ namespace FLEXIMG_NAMESPACE { // enum class PrepareStatus : int { - // 最終状態(exec() の戻り値として使用) - Prepared = 0, // 準備完了(成功) - CycleError = 1, // 循環参照を検出 - NoUpstream = 2, // 上流ノードが未接続 - NoDownstream = 3, // 下流ノードが未接続 - - // 中間状態(prepare フェーズ中の一時的な状態) - Idle = -2, // 未処理(初期状態) - Preparing = -1, // 準備中(循環検出用) + // 最終状態(exec() の戻り値として使用) + Prepared = 0, // 準備完了(成功) + CycleError = 1, // 循環参照を検出 + NoUpstream = 2, // 上流ノードが未接続 + NoDownstream = 3, // 下流ノードが未接続 + + // 中間状態(prepare フェーズ中の一時的な状態) + Idle = -2, // 未処理(初期状態) + Preparing = -1, // 準備中(循環検出用) }; // ======================================================================== @@ -52,15 +52,18 @@ enum class PrepareStatus : int { // ======================================================================== struct TileConfig { - int16_t tileWidth = 0; // 0 = 分割なし - int16_t tileHeight = 0; + int16_t tileWidth = 0; // 0 = 分割なし + int16_t tileHeight = 0; - TileConfig() = default; - TileConfig(int_fast16_t w, int_fast16_t h) - : tileWidth(static_cast(w)), - tileHeight(static_cast(h)) {} + TileConfig() = default; + TileConfig(int_fast16_t w, int_fast16_t h) : tileWidth(static_cast(w)), tileHeight(static_cast(h)) + { + } - bool isEnabled() const { return tileWidth > 0 && tileHeight > 0; } + bool isEnabled() const + { + return tileWidth > 0 && tileHeight > 0; + } }; // ======================================================================== @@ -68,21 +71,25 @@ struct TileConfig { // ======================================================================== struct RenderRequest { - int16_t width = 0; - int16_t height = 0; - Point origin; // バッファ内での基準点位置(固定小数点 Q16.16) - - bool isEmpty() const { return width <= 0 || height <= 0; } - - // マージン分拡大(フィルタ用) - // 左右上下に適用されるため width/height は margin*2 増加 - // origin は左上に移動(ワールド座標なので減算) - RenderRequest expand(int_fast16_t margin) const { - int_fixed marginFixed = to_fixed(margin); - return {static_cast(width + margin * 2), - static_cast(height + margin * 2), - {origin.x - marginFixed, origin.y - marginFixed}}; - } + int16_t width = 0; + int16_t height = 0; + Point origin; // バッファ内での基準点位置(固定小数点 Q16.16) + + bool isEmpty() const + { + return width <= 0 || height <= 0; + } + + // マージン分拡大(フィルタ用) + // 左右上下に適用されるため width/height は margin*2 増加 + // origin は左上に移動(ワールド座標なので減算) + RenderRequest expand(int_fast16_t margin) const + { + int_fixed marginFixed = to_fixed(margin); + return {static_cast(width + margin * 2), + static_cast(height + margin * 2), + {origin.x - marginFixed, origin.y - marginFixed}}; + } }; // ======================================================================== @@ -94,23 +101,23 @@ struct RenderRequest { // struct PrepareRequest { - int16_t width = 0; - int16_t height = 0; - Point origin; // 基準点位置(固定小数点 Q16.16) + int16_t width = 0; + int16_t height = 0; + Point origin; // 基準点位置(固定小数点 Q16.16) - // プル型アフィン(上流→Source で実行) - AffineMatrix affineMatrix; - bool hasAffine = false; + // プル型アフィン(上流→Source で実行) + AffineMatrix affineMatrix; + bool hasAffine = false; - // プッシュ型アフィン(下流→Sink で実行) - AffineMatrix pushAffineMatrix; - bool hasPushAffine = false; + // プッシュ型アフィン(下流→Sink で実行) + AffineMatrix pushAffineMatrix; + bool hasPushAffine = false; - // レンダリングコンテキスト(RendererNodeから伝播、allocator+entryPoolを統合) - RenderContext *context = nullptr; + // レンダリングコンテキスト(RendererNodeから伝播、allocator+entryPoolを統合) + RenderContext *context = nullptr; - // 希望フォーマット(下流から上流へ伝播、フォーマット交渉用) - PixelFormatID preferredFormat = PixelFormatIDs::RGBA8_Straight; + // 希望フォーマット(下流から上流へ伝播、フォーマット交渉用) + PixelFormatID preferredFormat = PixelFormatIDs::RGBA8_Straight; }; // ======================================================================== @@ -124,93 +131,91 @@ struct PrepareRequest { // DataRange は data_range.h で定義 struct PrepareResponse { - PrepareStatus status = PrepareStatus::Idle; - - // === AABBバウンディングボックス(処理すべき範囲) === - int16_t width = 0; - int16_t height = 0; - Point origin; - - // === フォーマット情報 === - PixelFormatID preferredFormat = PixelFormatIDs::RGBA8_Straight; - - // 便利メソッド - bool ok() const { return status == PrepareStatus::Prepared; } - - // 要求矩形との交差判定 - // このAABBとrequestの矩形が重なるかどうかを判定 - bool intersects(const RenderRequest &request) const { - // サイズ0は交差しない - if (width <= 0 || height <= 0) - return false; - if (request.width <= 0 || request.height <= 0) - return false; - - // 各矩形のワールド座標範囲を計算 - // AABB(this)の範囲(originはバッファ左上のワールド座標) - float aabbLeft = fixed_to_float(origin.x); - float aabbTop = fixed_to_float(origin.y); - float aabbRight = aabbLeft + static_cast(width); - float aabbBottom = aabbTop + static_cast(height); - - // リクエスト矩形の範囲(originはリクエスト左上のワールド座標) - float reqLeft = fixed_to_float(request.origin.x); - float reqTop = fixed_to_float(request.origin.y); - float reqRight = reqLeft + static_cast(request.width); - float reqBottom = reqTop + static_cast(request.height); - - // 矩形が重ならない条件の否定 - return !(aabbRight <= reqLeft || reqRight <= aabbLeft || - aabbBottom <= reqTop || reqBottom <= aabbTop); - } - - // 要求矩形との交差範囲を取得(X方向) - // request座標系でのX方向有効範囲を返す - DataRange getDataRange(const RenderRequest &request) const { - // サイズ0は空範囲 - if (width <= 0 || height <= 0) - return DataRange{0, 0}; - if (request.width <= 0 || request.height <= 0) - return DataRange{0, 0}; - - // 各矩形のワールド座標範囲を計算 - // originはバッファ左上のワールド座標 - float aabbLeft = fixed_to_float(origin.x); - float aabbTop = fixed_to_float(origin.y); - float aabbRight = aabbLeft + static_cast(width); - float aabbBottom = aabbTop + static_cast(height); - - // リクエスト範囲(originはリクエスト左上のワールド座標) - float reqLeft = fixed_to_float(request.origin.x); - float reqTop = fixed_to_float(request.origin.y); - float reqRight = reqLeft + static_cast(request.width); - float reqBottom = reqTop + static_cast(request.height); - - // Y方向の交差判定(交差しなければ空範囲) - if (aabbBottom <= reqTop || reqBottom <= aabbTop) { - return DataRange{0, 0}; - } + PrepareStatus status = PrepareStatus::Idle; - // X方向の交差範囲を計算 - float intersectLeft = (aabbLeft > reqLeft) ? aabbLeft : reqLeft; - float intersectRight = (aabbRight < reqRight) ? aabbRight : reqRight; + // === AABBバウンディングボックス(処理すべき範囲) === + int16_t width = 0; + int16_t height = 0; + Point origin; - if (intersectRight <= intersectLeft) { - return DataRange{0, 0}; - } + // === フォーマット情報 === + PixelFormatID preferredFormat = PixelFormatIDs::RGBA8_Straight; - // request座標系に変換(reqLeftが0になる座標系) - int16_t startX = static_cast(intersectLeft - reqLeft); - int16_t endX = static_cast(std::ceil(intersectRight - reqLeft)); + // 便利メソッド + bool ok() const + { + return status == PrepareStatus::Prepared; + } - // request.width内にクランプ - if (startX < 0) - startX = 0; - if (endX > request.width) - endX = request.width; + // 要求矩形との交差判定 + // このAABBとrequestの矩形が重なるかどうかを判定 + bool intersects(const RenderRequest &request) const + { + // サイズ0は交差しない + if (width <= 0 || height <= 0) return false; + if (request.width <= 0 || request.height <= 0) return false; + + // 各矩形のワールド座標範囲を計算 + // AABB(this)の範囲(originはバッファ左上のワールド座標) + float aabbLeft = fixed_to_float(origin.x); + float aabbTop = fixed_to_float(origin.y); + float aabbRight = aabbLeft + static_cast(width); + float aabbBottom = aabbTop + static_cast(height); + + // リクエスト矩形の範囲(originはリクエスト左上のワールド座標) + float reqLeft = fixed_to_float(request.origin.x); + float reqTop = fixed_to_float(request.origin.y); + float reqRight = reqLeft + static_cast(request.width); + float reqBottom = reqTop + static_cast(request.height); + + // 矩形が重ならない条件の否定 + return !(aabbRight <= reqLeft || reqRight <= aabbLeft || aabbBottom <= reqTop || reqBottom <= aabbTop); + } - return DataRange{startX, endX}; - } + // 要求矩形との交差範囲を取得(X方向) + // request座標系でのX方向有効範囲を返す + DataRange getDataRange(const RenderRequest &request) const + { + // サイズ0は空範囲 + if (width <= 0 || height <= 0) return DataRange{0, 0}; + if (request.width <= 0 || request.height <= 0) return DataRange{0, 0}; + + // 各矩形のワールド座標範囲を計算 + // originはバッファ左上のワールド座標 + float aabbLeft = fixed_to_float(origin.x); + float aabbTop = fixed_to_float(origin.y); + float aabbRight = aabbLeft + static_cast(width); + float aabbBottom = aabbTop + static_cast(height); + + // リクエスト範囲(originはリクエスト左上のワールド座標) + float reqLeft = fixed_to_float(request.origin.x); + float reqTop = fixed_to_float(request.origin.y); + float reqRight = reqLeft + static_cast(request.width); + float reqBottom = reqTop + static_cast(request.height); + + // Y方向の交差判定(交差しなければ空範囲) + if (aabbBottom <= reqTop || reqBottom <= aabbTop) { + return DataRange{0, 0}; + } + + // X方向の交差範囲を計算 + float intersectLeft = (aabbLeft > reqLeft) ? aabbLeft : reqLeft; + float intersectRight = (aabbRight < reqRight) ? aabbRight : reqRight; + + if (intersectRight <= intersectLeft) { + return DataRange{0, 0}; + } + + // request座標系に変換(reqLeftが0になる座標系) + int16_t startX = static_cast(intersectLeft - reqLeft); + int16_t endX = static_cast(std::ceil(intersectRight - reqLeft)); + + // request.width内にクランプ + if (startX < 0) startX = 0; + if (endX > request.width) endX = request.width; + + return DataRange{startX, endX}; + } }; // ======================================================================== @@ -236,43 +241,42 @@ struct PrepareResponse { // - 共通乗算の事前計算(a*left, a*right, c*left, c*right)で乗算4回削減 // - tx/ty の加算を最後に1回だけ行う(8回→2回) // - std::min/max の initializer_list 版で簡潔に記述 -inline void calcAffineAABB(float inputWidth, float inputHeight, - Point inputOrigin, const AffineMatrix &matrix, - int16_t &outWidth, int16_t &outHeight, - Point &outOrigin) { - // 入力矩形の4角(pivot を原点とした相対座標) - const float left = -fixed_to_float(inputOrigin.x); - const float right = left + inputWidth; - const float top = -fixed_to_float(inputOrigin.y); - const float bottom = top + inputHeight; - - // X座標: 4角をアフィン変換してAABBを計算 - // x' = a*x + b*y + tx (tx は最後に加算) - const float al = matrix.a * left; - const float ar = matrix.a * right; - const float x0 = al + matrix.b * top; - const float x1 = ar + matrix.b * top; - const float x2 = al + matrix.b * bottom; - const float x3 = ar + matrix.b * bottom; - const float minX = std::min({x0, x1, x2, x3}); - const float maxX = std::max({x0, x1, x2, x3}); - - outWidth = static_cast(std::ceil(maxX - minX)); - outOrigin.x = float_to_fixed(minX + matrix.tx); - - // Y座標: 同様に計算 - // y' = c*x + d*y + ty (ty は最後に加算) - const float cl = matrix.c * left; - const float cr = matrix.c * right; - const float y0 = cl + matrix.d * top; - const float y1 = cr + matrix.d * top; - const float y2 = cl + matrix.d * bottom; - const float y3 = cr + matrix.d * bottom; - const float minY = std::min({y0, y1, y2, y3}); - const float maxY = std::max({y0, y1, y2, y3}); - - outHeight = static_cast(std::ceil(maxY - minY)); - outOrigin.y = float_to_fixed(minY + matrix.ty); +inline void calcAffineAABB(float inputWidth, float inputHeight, Point inputOrigin, const AffineMatrix &matrix, + int16_t &outWidth, int16_t &outHeight, Point &outOrigin) +{ + // 入力矩形の4角(pivot を原点とした相対座標) + const float left = -fixed_to_float(inputOrigin.x); + const float right = left + inputWidth; + const float top = -fixed_to_float(inputOrigin.y); + const float bottom = top + inputHeight; + + // X座標: 4角をアフィン変換してAABBを計算 + // x' = a*x + b*y + tx (tx は最後に加算) + const float al = matrix.a * left; + const float ar = matrix.a * right; + const float x0 = al + matrix.b * top; + const float x1 = ar + matrix.b * top; + const float x2 = al + matrix.b * bottom; + const float x3 = ar + matrix.b * bottom; + const float minX = std::min({x0, x1, x2, x3}); + const float maxX = std::max({x0, x1, x2, x3}); + + outWidth = static_cast(std::ceil(maxX - minX)); + outOrigin.x = float_to_fixed(minX + matrix.tx); + + // Y座標: 同様に計算 + // y' = c*x + d*y + ty (ty は最後に加算) + const float cl = matrix.c * left; + const float cr = matrix.c * right; + const float y0 = cl + matrix.d * top; + const float y1 = cr + matrix.d * top; + const float y2 = cl + matrix.d * bottom; + const float y3 = cr + matrix.d * bottom; + const float minY = std::min({y0, y1, y2, y3}); + const float maxY = std::max({y0, y1, y2, y3}); + + outHeight = static_cast(std::ceil(maxY - minY)); + outOrigin.y = float_to_fixed(minY + matrix.ty); } // 出力矩形から逆変換で必要な入力範囲を計算 @@ -280,33 +284,31 @@ inline void calcAffineAABB(float inputWidth, float inputHeight, // outputOrigin: 出力矩形の基準点(固定小数点) // matrix: 順方向のアフィン変換(内部で逆行列を計算) // 戻り値: 入力側で必要なAABB(width, height, origin) -inline void calcInverseAffineAABB(int_fast16_t outputWidth, - int_fast16_t outputHeight, Point outputOrigin, - const AffineMatrix &matrix, int16_t &outWidth, - int16_t &outHeight, Point &outOrigin) { - // 逆行列を計算 - float det = matrix.a * matrix.d - matrix.b * matrix.c; - if (std::abs(det) < 1e-10f) { - // 特異行列の場合はそのまま返す - outWidth = static_cast(outputWidth); - outHeight = static_cast(outputHeight); - outOrigin = outputOrigin; - return; - } - - float invDet = 1.0f / det; - AffineMatrix inv(matrix.d * invDet, // a - -matrix.b * invDet, // b - -matrix.c * invDet, // c - matrix.a * invDet, // d - (matrix.b * matrix.ty - matrix.d * matrix.tx) * invDet, // tx - (matrix.c * matrix.tx - matrix.a * matrix.ty) * invDet // ty - ); - - // 逆行列で変換 - calcAffineAABB(static_cast(outputWidth), - static_cast(outputHeight), outputOrigin, inv, outWidth, - outHeight, outOrigin); +inline void calcInverseAffineAABB(int_fast16_t outputWidth, int_fast16_t outputHeight, Point outputOrigin, + const AffineMatrix &matrix, int16_t &outWidth, int16_t &outHeight, Point &outOrigin) +{ + // 逆行列を計算 + float det = matrix.a * matrix.d - matrix.b * matrix.c; + if (std::abs(det) < 1e-10f) { + // 特異行列の場合はそのまま返す + outWidth = static_cast(outputWidth); + outHeight = static_cast(outputHeight); + outOrigin = outputOrigin; + return; + } + + float invDet = 1.0f / det; + AffineMatrix inv(matrix.d * invDet, // a + -matrix.b * invDet, // b + -matrix.c * invDet, // c + matrix.a * invDet, // d + (matrix.b * matrix.ty - matrix.d * matrix.tx) * invDet, // tx + (matrix.c * matrix.tx - matrix.a * matrix.ty) * invDet // ty + ); + + // 逆行列で変換 + calcAffineAABB(static_cast(outputWidth), static_cast(outputHeight), outputOrigin, inv, outWidth, + outHeight, outOrigin); } // ======================================================================== @@ -320,147 +322,171 @@ inline void calcInverseAffineAABB(int_fast16_t outputWidth, // struct RenderResponse { - Point origin; // バッファ左上のワールド座標(固定小数点 Q16.16) - bool inUse = false; // プール管理用:使用中フラグ - - // デフォルトコンストラクタ - RenderResponse() = default; - - // ムーブのみ(コピー禁止) - RenderResponse(const RenderResponse &) = delete; - RenderResponse &operator=(const RenderResponse &) = delete; - RenderResponse(RenderResponse &&) = default; - RenderResponse &operator=(RenderResponse &&) = default; - - // ======================================== - // 設定(RenderContext::setup から呼ばれる) - // ======================================== - - void setPool(ImageBufferEntryPool *p) { pool_ = p; } - void setAllocator(core::memory::IAllocator *a) { allocator_ = a; } - - // ======================================== - // 有効性判定 - // ======================================== - - /// @brief 有効なバッファを持っているか - bool isValid() const { return entry_ != nullptr; } - - /// @brief バッファを持っているか - bool hasBuffer() const { return entry_ != nullptr; } - - /// @brief 空かどうか - bool empty() const { return entry_ == nullptr; } - - /// @brief バッファ数を取得(常に 0 or 1) - int bufferCount() const { return entry_ ? 1 : 0; } - - // ======================================== - // バッファアクセス - // ======================================== - - /// @brief バッファを取得 - ImageBuffer &buffer() { - FLEXIMG_ASSERT(entry_ != nullptr, "No buffer in RenderResponse"); - return entry_->buffer; - } - - const ImageBuffer &buffer() const { - FLEXIMG_ASSERT(entry_ != nullptr, "No buffer in RenderResponse"); - return entry_->buffer; - } - - /// @brief バッファのビューを取得 - ViewPort view() { return entry_ ? entry_->buffer.view() : ViewPort(); } - - ViewPort view() const { return entry_ ? entry_->buffer.view() : ViewPort(); } - - // ======================================== - // バッファ管理 - // ======================================== - - /// @brief 新しいバッファを直接作成 - /// @return 作成されたバッファへのポインタ(失敗時はnullptr) - ImageBuffer *createBuffer(int_fast16_t width, int_fast16_t height, - PixelFormatID format, InitPolicy policy) { - if (width <= 0 || height <= 0 || !format) - return nullptr; - // 既存エントリがあれば解放 - releaseEntry(); - // プールから取得 - entry_ = pool_ ? pool_->acquire() : nullptr; - if (!entry_) - return nullptr; - entry_->buffer = ImageBuffer(width, height, format, policy, allocator_); - if (!entry_->buffer.isValid()) { - releaseEntry(); - return nullptr; + Point origin; // バッファ左上のワールド座標(固定小数点 Q16.16) + bool inUse = false; // プール管理用:使用中フラグ + + // デフォルトコンストラクタ + RenderResponse() = default; + + // ムーブのみ(コピー禁止) + RenderResponse(const RenderResponse &) = delete; + RenderResponse &operator=(const RenderResponse &) = delete; + RenderResponse(RenderResponse &&) = default; + RenderResponse &operator=(RenderResponse &&) = default; + + // ======================================== + // 設定(RenderContext::setup から呼ばれる) + // ======================================== + + void setPool(ImageBufferEntryPool *p) + { + pool_ = p; } - return &entry_->buffer; - } - - /// @brief バッファを追加(ムーブ) - void addBuffer(ImageBuffer &&buf) { - if (!buf.isValid()) - return; - // 既存エントリがあれば解放 - releaseEntry(); - entry_ = pool_ ? pool_->acquire() : nullptr; - if (entry_) { - entry_->buffer = std::move(buf); + void setAllocator(core::memory::IAllocator *a) + { + allocator_ = a; } - } - - /// @brief バッファを入れ替え(originを保持) - void replaceBuffer(ImageBuffer &&buf) { - if (!entry_) - return; - Point savedOrigin = entry_->buffer.origin(); - entry_->buffer = std::move(buf); - entry_->buffer.setOrigin(savedOrigin); - } - - /// @brief バッファをクリア(エントリをプールに返却) - void clear() { releaseEntry(); } - - /// @brief バッファのフォーマットを変換 - void convertFormat(PixelFormatID format) { - if (!entry_ || !allocator_ || !format) - return; - PixelFormatID srcFmt = entry_->buffer.view().formatID; - if (srcFmt == format) - return; - - auto width = entry_->buffer.width(); - ImageBuffer converted(width, static_cast(1), format, - InitPolicy::Uninitialized, allocator_); - if (!converted.isValid()) - return; - - const void *srcRow = entry_->buffer.view().pixelAt(0, 0); - void *dstRow = converted.view().pixelAt(0, 0); - const PixelAuxInfo *auxInfo = &entry_->buffer.auxInfo(); - FLEXIMG_NAMESPACE::convertFormat(srcRow, srcFmt, dstRow, format, width, - auxInfo); - - Point savedOrigin = entry_->buffer.origin(); - entry_->buffer = std::move(converted); - entry_->buffer.setOrigin(savedOrigin); - } -private: - ImageBufferEntryPool::Entry *entry_ = nullptr; - ImageBufferEntryPool *pool_ = nullptr; - core::memory::IAllocator *allocator_ = nullptr; + // ======================================== + // 有効性判定 + // ======================================== + + /// @brief 有効なバッファを持っているか + bool isValid() const + { + return entry_ != nullptr; + } + + /// @brief バッファを持っているか + bool hasBuffer() const + { + return entry_ != nullptr; + } + + /// @brief 空かどうか + bool empty() const + { + return entry_ == nullptr; + } + + /// @brief バッファ数を取得(常に 0 or 1) + int bufferCount() const + { + return entry_ ? 1 : 0; + } + + // ======================================== + // バッファアクセス + // ======================================== + + /// @brief バッファを取得 + ImageBuffer &buffer() + { + FLEXIMG_ASSERT(entry_ != nullptr, "No buffer in RenderResponse"); + return entry_->buffer; + } + + const ImageBuffer &buffer() const + { + FLEXIMG_ASSERT(entry_ != nullptr, "No buffer in RenderResponse"); + return entry_->buffer; + } + + /// @brief バッファのビューを取得 + ViewPort view() + { + return entry_ ? entry_->buffer.view() : ViewPort(); + } + + ViewPort view() const + { + return entry_ ? entry_->buffer.view() : ViewPort(); + } - void releaseEntry() { - if (entry_ && pool_) { - pool_->release(entry_); + // ======================================== + // バッファ管理 + // ======================================== + + /// @brief 新しいバッファを直接作成 + /// @return 作成されたバッファへのポインタ(失敗時はnullptr) + ImageBuffer *createBuffer(int_fast16_t width, int_fast16_t height, PixelFormatID format, InitPolicy policy) + { + if (width <= 0 || height <= 0 || !format) return nullptr; + // 既存エントリがあれば解放 + releaseEntry(); + // プールから取得 + entry_ = pool_ ? pool_->acquire() : nullptr; + if (!entry_) return nullptr; + entry_->buffer = ImageBuffer(width, height, format, policy, allocator_); + if (!entry_->buffer.isValid()) { + releaseEntry(); + return nullptr; + } + return &entry_->buffer; + } + + /// @brief バッファを追加(ムーブ) + void addBuffer(ImageBuffer &&buf) + { + if (!buf.isValid()) return; + // 既存エントリがあれば解放 + releaseEntry(); + entry_ = pool_ ? pool_->acquire() : nullptr; + if (entry_) { + entry_->buffer = std::move(buf); + } + } + + /// @brief バッファを入れ替え(originを保持) + void replaceBuffer(ImageBuffer &&buf) + { + if (!entry_) return; + Point savedOrigin = entry_->buffer.origin(); + entry_->buffer = std::move(buf); + entry_->buffer.setOrigin(savedOrigin); + } + + /// @brief バッファをクリア(エントリをプールに返却) + void clear() + { + releaseEntry(); + } + + /// @brief バッファのフォーマットを変換 + void convertFormat(PixelFormatID format) + { + if (!entry_ || !allocator_ || !format) return; + PixelFormatID srcFmt = entry_->buffer.view().formatID; + if (srcFmt == format) return; + + auto width = entry_->buffer.width(); + ImageBuffer converted(width, static_cast(1), format, InitPolicy::Uninitialized, allocator_); + if (!converted.isValid()) return; + + const void *srcRow = entry_->buffer.view().pixelAt(0, 0); + void *dstRow = converted.view().pixelAt(0, 0); + const PixelAuxInfo *auxInfo = &entry_->buffer.auxInfo(); + FLEXIMG_NAMESPACE::convertFormat(srcRow, srcFmt, dstRow, format, width, auxInfo); + + Point savedOrigin = entry_->buffer.origin(); + entry_->buffer = std::move(converted); + entry_->buffer.setOrigin(savedOrigin); + } + +private: + ImageBufferEntryPool::Entry *entry_ = nullptr; + ImageBufferEntryPool *pool_ = nullptr; + core::memory::IAllocator *allocator_ = nullptr; + + void releaseEntry() + { + if (entry_ && pool_) { + pool_->release(entry_); + } + entry_ = nullptr; } - entry_ = nullptr; - } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_RENDER_TYPES_H +#endif // FLEXIMG_RENDER_TYPES_H diff --git a/src/fleximg/image/viewport.h b/src/fleximg/image/viewport.h index 0dfe35f..fa8aa87 100644 --- a/src/fleximg/image/viewport.h +++ b/src/fleximg/image/viewport.h @@ -21,53 +21,62 @@ namespace FLEXIMG_NAMESPACE { // struct ViewPort { - void *data = nullptr; // 常にバッファ全体の先頭を指す - PixelFormatID formatID = PixelFormatIDs::RGBA8_Straight; - int32_t stride = 0; // 負値でY軸反転対応 - int16_t width = 0; - int16_t height = 0; - int16_t x = 0; // バッファ内でのビュー左上のX座標 - int16_t y = 0; // バッファ内でのビュー左上のY座標 - - // デフォルトコンストラクタ - ViewPort() = default; - - // 直接初期化(引数は最速型、メンバ格納時にキャスト) - ViewPort(void *d, PixelFormatID fmt, int32_t str, int_fast16_t w, - int_fast16_t h) - : data(d), formatID(fmt), stride(str), width(static_cast(w)), - height(static_cast(h)) {} - - // 簡易初期化(strideを自動計算) - ViewPort(void *d, int_fast16_t w, int_fast16_t h, - PixelFormatID fmt = PixelFormatIDs::RGBA8_Straight) - : data(d), formatID(fmt), - stride(static_cast(w * fmt->bytesPerPixel)), - width(static_cast(w)), height(static_cast(h)) {} - - // 有効判定 - bool isValid() const { return data != nullptr && width > 0 && height > 0; } - - // ピクセルアドレス取得(strideが負の場合もサポート) - void *pixelAt(int localX, int localY) { - return static_cast(data) + - static_cast(this->y + localY) * stride + - (this->x + localX) * formatID->bytesPerPixel; - } - - const void *pixelAt(int localX, int localY) const { - return static_cast(data) + - static_cast(this->y + localY) * stride + - (this->x + localX) * formatID->bytesPerPixel; - } - - // バイト情報 - uint8_t bytesPerPixel() const { return formatID->bytesPerPixel; } - uint32_t rowBytes() const { - return stride > 0 ? static_cast(stride) - : static_cast(width) * - static_cast(bytesPerPixel()); - } + void *data = nullptr; // 常にバッファ全体の先頭を指す + PixelFormatID formatID = PixelFormatIDs::RGBA8_Straight; + int32_t stride = 0; // 負値でY軸反転対応 + int16_t width = 0; + int16_t height = 0; + int16_t x = 0; // バッファ内でのビュー左上のX座標 + int16_t y = 0; // バッファ内でのビュー左上のY座標 + + // デフォルトコンストラクタ + ViewPort() = default; + + // 直接初期化(引数は最速型、メンバ格納時にキャスト) + ViewPort(void *d, PixelFormatID fmt, int32_t str, int_fast16_t w, int_fast16_t h) + : data(d), formatID(fmt), stride(str), width(static_cast(w)), height(static_cast(h)) + { + } + + // 簡易初期化(strideを自動計算) + ViewPort(void *d, int_fast16_t w, int_fast16_t h, PixelFormatID fmt = PixelFormatIDs::RGBA8_Straight) + : data(d), + formatID(fmt), + stride(static_cast(w * fmt->bytesPerPixel)), + width(static_cast(w)), + height(static_cast(h)) + { + } + + // 有効判定 + bool isValid() const + { + return data != nullptr && width > 0 && height > 0; + } + + // ピクセルアドレス取得(strideが負の場合もサポート) + void *pixelAt(int localX, int localY) + { + return static_cast(data) + static_cast(this->y + localY) * stride + + (this->x + localX) * formatID->bytesPerPixel; + } + + const void *pixelAt(int localX, int localY) const + { + return static_cast(data) + static_cast(this->y + localY) * stride + + (this->x + localX) * formatID->bytesPerPixel; + } + + // バイト情報 + uint8_t bytesPerPixel() const + { + return formatID->bytesPerPixel; + } + uint32_t rowBytes() const + { + return stride > 0 ? static_cast(stride) + : static_cast(width) * static_cast(bytesPerPixel()); + } }; // ======================================================================== @@ -77,25 +86,23 @@ struct ViewPort { namespace view_ops { // サブビュー作成(引数は最速型、32bitマイコンでのビット切り詰め回避) -inline ViewPort subView(const ViewPort &v, int_fast16_t dx, int_fast16_t dy, - int_fast16_t w, int_fast16_t h) { - ViewPort result = v; - result.x = static_cast(v.x + dx); // オフセット累積 - result.y = static_cast(v.y + dy); // オフセット累積 - result.width = static_cast(w); - result.height = static_cast(h); - // data, stride, formatID は変更しない(常にバッファ全体を指す) - return result; +inline ViewPort subView(const ViewPort &v, int_fast16_t dx, int_fast16_t dy, int_fast16_t w, int_fast16_t h) +{ + ViewPort result = v; + result.x = static_cast(v.x + dx); // オフセット累積 + result.y = static_cast(v.y + dy); // オフセット累積 + result.width = static_cast(w); + result.height = static_cast(h); + // data, stride, formatID は変更しない(常にバッファ全体を指す) + return result; } // 矩形コピー -void copy(ViewPort &dst, int_fast16_t dstX, int_fast16_t dstY, - const ViewPort &src, int_fast16_t srcX, int_fast16_t srcY, - int_fast16_t width, int_fast16_t height); +void copy(ViewPort &dst, int_fast16_t dstX, int_fast16_t dstY, const ViewPort &src, int_fast16_t srcX, + int_fast16_t srcY, int_fast16_t width, int_fast16_t height); // 矩形クリア -void clear(ViewPort &dst, int_fast16_t x, int_fast16_t y, int_fast16_t width, - int_fast16_t height); +void clear(ViewPort &dst, int_fast16_t x, int_fast16_t y, int_fast16_t width, int_fast16_t height); // ======================================================================== // DDA転写関数 @@ -112,8 +119,7 @@ void clear(ViewPort &dst, int_fast16_t x, int_fast16_t y, int_fast16_t width, // count: 転写ピクセル数 // srcX, srcY: ソース開始座標(Q16.16固定小数点) // incrX, incrY: 1ピクセルあたりの増分(Q16.16固定小数点) -void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, - int_fixed srcX, int_fixed srcY, int_fixed incrX, +void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, int_fixed srcX, int_fixed srcY, int_fixed incrX, int_fixed incrY); // DDA行転写(バイリニア補間) @@ -122,33 +128,29 @@ void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, // edgeFadeMask: // EdgeFadeFlagsの値。フェード有効な辺のみ境界ピクセルのアルファを0化 srcAux: // パレット情報等(Index8のパレット展開に使用) -void copyRowDDABilinear(void *dst, const ViewPort &src, int_fast16_t count, - int_fixed srcX, int_fixed srcY, int_fixed incrX, - int_fixed incrY, uint8_t edgeFadeMask, - const PixelAuxInfo *srcAux); +void copyRowDDABilinear(void *dst, const ViewPort &src, int_fast16_t count, int_fixed srcX, int_fixed srcY, + int_fixed incrX, int_fixed incrY, uint8_t edgeFadeMask, const PixelAuxInfo *srcAux); // アフィン変換転写(DDA方式) // 複数行を一括処理する高レベル関数 -void affineTransform(ViewPort &dst, const ViewPort &src, int_fixed invTx, - int_fixed invTy, const Matrix2x2_fixed &invMatrix, - int_fixed rowOffsetX, int_fixed rowOffsetY, - int_fixed dxOffsetX, int_fixed dxOffsetY); +void affineTransform(ViewPort &dst, const ViewPort &src, int_fixed invTx, int_fixed invTy, + const Matrix2x2_fixed &invMatrix, int_fixed rowOffsetX, int_fixed rowOffsetY, int_fixed dxOffsetX, + int_fixed dxOffsetY); // 1chバイリニア補間が使用可能かを判定するヘルパー // Alpha8, Grayscale8 等の単一チャンネル非インデックスフォーマットに適用 // edgeFadeMask != 0 の場合、Alphaチャンネルのみ対応(値を直接0にできるため) -inline bool canUseSingleChannelBilinear(PixelFormatID formatID, - uint8_t edgeFadeMask) { - if (!formatID) - return false; - const int bytesPerPixel = formatID->bytesPerPixel; - return (bytesPerPixel == 1) && (formatID->channelCount == 1) && - !formatID->isIndexed && (edgeFadeMask == 0 || formatID->hasAlpha); +inline bool canUseSingleChannelBilinear(PixelFormatID formatID, uint8_t edgeFadeMask) +{ + if (!formatID) return false; + const int bytesPerPixel = formatID->bytesPerPixel; + return (bytesPerPixel == 1) && (formatID->channelCount == 1) && !formatID->isIndexed && + (edgeFadeMask == 0 || formatID->hasAlpha); } -} // namespace view_ops +} // namespace view_ops -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -162,101 +164,91 @@ inline bool canUseSingleChannelBilinear(PixelFormatID formatID, namespace FLEXIMG_NAMESPACE { namespace view_ops { -void copy(ViewPort &dst, int_fast16_t dstX, int_fast16_t dstY, - const ViewPort &src, int_fast16_t srcX, int_fast16_t srcY, - int_fast16_t width, int_fast16_t height) { - if (!dst.isValid() || !src.isValid()) - return; - - // クリッピング - if (srcX < 0) { - dstX -= srcX; - width += srcX; - srcX = 0; - } - if (srcY < 0) { - dstY -= srcY; - height += srcY; - srcY = 0; - } - if (dstX < 0) { - srcX -= dstX; - width += dstX; - dstX = 0; - } - if (dstY < 0) { - srcY -= dstY; - height += dstY; - dstY = 0; - } - width = std::min( - width, std::min(src.width - srcX, dst.width - dstX)); - height = std::min( - height, std::min(src.height - srcY, dst.height - dstY)); - if (width <= 0 || height <= 0) - return; - - // view_ops::copy は同一フォーマット間の矩形コピー専用。 - // 異フォーマット間変換は resolveConverter / convertFormat - // を直接使用すること。 - FLEXIMG_ASSERT(src.formatID == dst.formatID, - "view_ops::copy requires matching formats; use convertFormat " - "for conversion"); - - size_t bytesPerPixel = static_cast(dst.bytesPerPixel()); - for (int_fast16_t y = 0; y < height; ++y) { - const uint8_t *srcRow = - static_cast(src.pixelAt(srcX, srcY + y)); - uint8_t *dstRow = static_cast(dst.pixelAt(dstX, dstY + y)); - std::memcpy(dstRow, srcRow, static_cast(width) * bytesPerPixel); - } +void copy(ViewPort &dst, int_fast16_t dstX, int_fast16_t dstY, const ViewPort &src, int_fast16_t srcX, + int_fast16_t srcY, int_fast16_t width, int_fast16_t height) +{ + if (!dst.isValid() || !src.isValid()) return; + + // クリッピング + if (srcX < 0) { + dstX -= srcX; + width += srcX; + srcX = 0; + } + if (srcY < 0) { + dstY -= srcY; + height += srcY; + srcY = 0; + } + if (dstX < 0) { + srcX -= dstX; + width += dstX; + dstX = 0; + } + if (dstY < 0) { + srcY -= dstY; + height += dstY; + dstY = 0; + } + width = std::min(width, std::min(src.width - srcX, dst.width - dstX)); + height = std::min(height, std::min(src.height - srcY, dst.height - dstY)); + if (width <= 0 || height <= 0) return; + + // view_ops::copy は同一フォーマット間の矩形コピー専用。 + // 異フォーマット間変換は resolveConverter / convertFormat + // を直接使用すること。 + FLEXIMG_ASSERT(src.formatID == dst.formatID, + "view_ops::copy requires matching formats; use convertFormat " + "for conversion"); + + size_t bytesPerPixel = static_cast(dst.bytesPerPixel()); + for (int_fast16_t y = 0; y < height; ++y) { + const uint8_t *srcRow = static_cast(src.pixelAt(srcX, srcY + y)); + uint8_t *dstRow = static_cast(dst.pixelAt(dstX, dstY + y)); + std::memcpy(dstRow, srcRow, static_cast(width) * bytesPerPixel); + } } -void clear(ViewPort &dst, int_fast16_t x, int_fast16_t y, int_fast16_t width, - int_fast16_t height) { - if (!dst.isValid()) - return; - - size_t bytesPerPixel = static_cast(dst.bytesPerPixel()); - for (int_fast16_t row = 0; row < height; ++row) { - auto dy = static_cast(y + row); - if (dy < 0 || dy >= dst.height) - continue; - uint8_t *dstRow = static_cast(dst.pixelAt(x, dy)); - std::memset(dstRow, 0, static_cast(width) * bytesPerPixel); - } +void clear(ViewPort &dst, int_fast16_t x, int_fast16_t y, int_fast16_t width, int_fast16_t height) +{ + if (!dst.isValid()) return; + + size_t bytesPerPixel = static_cast(dst.bytesPerPixel()); + for (int_fast16_t row = 0; row < height; ++row) { + auto dy = static_cast(y + row); + if (dy < 0 || dy >= dst.height) continue; + uint8_t *dstRow = static_cast(dst.pixelAt(x, dy)); + std::memset(dstRow, 0, static_cast(width) * bytesPerPixel); + } } // ============================================================================ // DDA転写関数 - 実装 // ============================================================================ -} // namespace view_ops +} // namespace view_ops namespace view_ops { -void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, - int_fixed srcX, int_fixed srcY, int_fixed incrX, - int_fixed incrY) { - if (!src.isValid() || count <= 0) - return; - - // ViewPortのオフセットを固定小数点に変換して加算 - int_fixed offsetX = static_cast(src.x) << INT_FIXED_SHIFT; - int_fixed offsetY = static_cast(src.y) << INT_FIXED_SHIFT; - - // DDAParam を構築(copyRowDDAでは srcWidth/srcHeight/weights は使用しない) - DDAParam param = {src.stride, 0, 0, - srcX + offsetX, // オフセット加算 - srcY + offsetY, // オフセット加算 - incrX, incrY, nullptr, nullptr}; - - // フォーマットの関数ポインタを呼び出し - if (src.formatID && src.formatID->copyRowDDA) { - src.formatID->copyRowDDA(static_cast(dst), - static_cast(src.data), count, - ¶m); - } +void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, int_fixed srcX, int_fixed srcY, int_fixed incrX, + int_fixed incrY) +{ + if (!src.isValid() || count <= 0) return; + + // ViewPortのオフセットを固定小数点に変換して加算 + int_fixed offsetX = static_cast(src.x) << INT_FIXED_SHIFT; + int_fixed offsetY = static_cast(src.y) << INT_FIXED_SHIFT; + + // DDAParam を構築(copyRowDDAでは srcWidth/srcHeight/weights は使用しない) + DDAParam param = {src.stride, 0, 0, + srcX + offsetX, // オフセット加算 + srcY + offsetY, // オフセット加算 + incrX, incrY, nullptr, nullptr}; + + // フォーマットの関数ポインタを呼び出し + if (src.formatID && src.formatID->copyRowDDA) { + src.formatID->copyRowDDA(static_cast(dst), static_cast(src.data), count, ¶m); + } } // ============================================================================ @@ -269,50 +261,50 @@ void copyRowDDA(void *dst, const ViewPort &src, int_fast16_t count, // 出力: dst = 補間結果 × count(各4bytes、RGBA8888) // 注意: 両ポインタは4バイトアライメントが必要 -__attribute__((noinline)) static void bilinearBlend_RGBA8888( - uint32_t *__restrict__ dst, const uint32_t *__restrict__ quadPixels, - const BilinearWeightXY *__restrict__ weightsXY, int count) { - for (int i = 0; i < count; ++i) { - uint_fast8_t fy = weightsXY->fy; - uint_fast8_t fx = weightsXY->fx; - ++weightsXY; - - uint32_t f = static_cast(fx) * - ((256 - fy) | (static_cast(fy) << 16)); - uint8_t q11f = static_cast(f >> 24); - uint8_t q10f = static_cast(f >> 8); - uint8_t q01f = static_cast(((256 - fx) * fy) >> 8); - uint_fast16_t q00f = 256 - (q11f + q01f + q10f); - - // 4点を32bitでロード(境界外ピクセルは事前にゼロ埋め済み) - uint32_t q00 = quadPixels[0]; - uint32_t q10 = quadPixels[1]; - uint32_t q01 = quadPixels[2]; - uint32_t q11 = quadPixels[3]; - quadPixels += 4; - - // R,B(偶数バイト位置)をマスク - uint32_t result_rb = q00f * (q00 & 0xFF00FF); - // G,A(奇数バイト位置)をシフト&マスク - uint32_t result_ga = q00f * ((q00 >> 8) & 0xFF00FF); - - result_rb += q10f * (q10 & 0xFF00FF); - result_ga += q10f * ((q10 >> 8) & 0xFF00FF); - - result_rb += q01f * (q01 & 0xFF00FF); - result_ga += q01f * ((q01 >> 8) & 0xFF00FF); - - result_rb += q11f * (q11 & 0xFF00FF); - result_ga += q11f * ((q11 >> 8) & 0xFF00FF); - - // 結果を出力(リトルエンディアン: G,Aは上位バイトが正しい位置に来る) - auto dstBytes = reinterpret_cast(dst); - *dst = result_ga; - dstBytes[0] = static_cast(result_rb >> 8); // R - dstBytes[2] = static_cast(result_rb >> 24); // B - - ++dst; - } +__attribute__((noinline)) static void bilinearBlend_RGBA8888(uint32_t *__restrict__ dst, + const uint32_t *__restrict__ quadPixels, + const BilinearWeightXY *__restrict__ weightsXY, int count) +{ + for (int i = 0; i < count; ++i) { + uint_fast8_t fy = weightsXY->fy; + uint_fast8_t fx = weightsXY->fx; + ++weightsXY; + + uint32_t f = static_cast(fx) * ((256 - fy) | (static_cast(fy) << 16)); + uint8_t q11f = static_cast(f >> 24); + uint8_t q10f = static_cast(f >> 8); + uint8_t q01f = static_cast(((256 - fx) * fy) >> 8); + uint_fast16_t q00f = 256 - (q11f + q01f + q10f); + + // 4点を32bitでロード(境界外ピクセルは事前にゼロ埋め済み) + uint32_t q00 = quadPixels[0]; + uint32_t q10 = quadPixels[1]; + uint32_t q01 = quadPixels[2]; + uint32_t q11 = quadPixels[3]; + quadPixels += 4; + + // R,B(偶数バイト位置)をマスク + uint32_t result_rb = q00f * (q00 & 0xFF00FF); + // G,A(奇数バイト位置)をシフト&マスク + uint32_t result_ga = q00f * ((q00 >> 8) & 0xFF00FF); + + result_rb += q10f * (q10 & 0xFF00FF); + result_ga += q10f * ((q10 >> 8) & 0xFF00FF); + + result_rb += q01f * (q01 & 0xFF00FF); + result_ga += q01f * ((q01 >> 8) & 0xFF00FF); + + result_rb += q11f * (q11 & 0xFF00FF); + result_ga += q11f * ((q11 >> 8) & 0xFF00FF); + + // 結果を出力(リトルエンディアン: G,Aは上位バイトが正しい位置に来る) + auto dstBytes = reinterpret_cast(dst); + *dst = result_ga; + dstBytes[0] = static_cast(result_rb >> 8); // R + dstBytes[2] = static_cast(result_rb >> 24); // B + + ++dst; + } } // ============================================================================ @@ -324,26 +316,26 @@ __attribute__((noinline)) static void bilinearBlend_RGBA8888( // 境界外ピクセルは呼び出し前にゼロ埋めされていること // 出力: dst = 補間結果 × count(各1byte) -__attribute__((noinline)) static void -bilinearBlend_1ch(uint8_t *__restrict__ dst, - const uint8_t *__restrict__ quadPixels, - const BilinearWeightXY *__restrict__ weightsXY, int count) { - for (int i = 0; i < count; ++i) { - // 重み計算 - uint32_t q4 = *reinterpret_cast(quadPixels); - uint_fast32_t fy = weightsXY->fy; - uint_fast32_t fx = weightsXY->fx; - ++weightsXY; - uint32_t left = (q4 & 0x00FF00FF); // 左側ピクセルの抽出 - uint32_t right = (q4 >> 8) & 0x00FF00FF; // 右側ピクセルの抽出 - uint32_t tb = left * (256 - fx) + right * fx; // 左右補間 - uint32_t top = (tb & 0x0000FFFF) * (256 - fy); - uint32_t bottom = (tb >> 16) * fy; - uint32_t combined = top + bottom; - uint16_t result = static_cast(combined >> 16); - quadPixels += 4; - dst[i] = static_cast(result); - } +__attribute__((noinline)) static void bilinearBlend_1ch(uint8_t *__restrict__ dst, + const uint8_t *__restrict__ quadPixels, + const BilinearWeightXY *__restrict__ weightsXY, int count) +{ + for (int i = 0; i < count; ++i) { + // 重み計算 + uint32_t q4 = *reinterpret_cast(quadPixels); + uint_fast32_t fy = weightsXY->fy; + uint_fast32_t fx = weightsXY->fx; + ++weightsXY; + uint32_t left = (q4 & 0x00FF00FF); // 左側ピクセルの抽出 + uint32_t right = (q4 >> 8) & 0x00FF00FF; // 右側ピクセルの抽出 + uint32_t tb = left * (256 - fx) + right * fx; // 左右補間 + uint32_t top = (tb & 0x0000FFFF) * (256 - fy); + uint32_t bottom = (tb >> 16) * fy; + uint32_t combined = top + bottom; + uint16_t result = static_cast(combined >> 16); + quadPixels += 4; + dst[i] = static_cast(result); + } } // ============================================================================ @@ -357,220 +349,206 @@ bilinearBlend_1ch(uint8_t *__restrict__ dst, // d. bilinearBlend_RGBA8888: バイリニア補間 // -void copyRowDDABilinear(void *dst, const ViewPort &src, int_fast16_t count, - int_fixed srcX, int_fixed srcY, int_fixed incrX, - int_fixed incrY, uint8_t edgeFadeMask, - const PixelAuxInfo *srcAux) { - if (!src.isValid() || count <= 0) - return; - - // copyQuadDDA未対応フォーマットは最近傍フォールバック - if (!src.formatID || !src.formatID->copyQuadDDA) { - copyRowDDA(dst, src, count, srcX, srcY, incrX, incrY); - return; - } - - // ======================================================================== - // 1chパス: Alpha8/Grayscale8等の単一チャンネルフォーマット向け高速パス - // フォーマット変換不要、メモリ使用量1/4、演算量約1/4 - // ======================================================================== - if (canUseSingleChannelBilinear(src.formatID, edgeFadeMask)) { - constexpr int CHUNK_SIZE = 64; +void copyRowDDABilinear(void *dst, const ViewPort &src, int_fast16_t count, int_fixed srcX, int_fixed srcY, + int_fixed incrX, int_fixed incrY, uint8_t edgeFadeMask, const PixelAuxInfo *srcAux) +{ + if (!src.isValid() || count <= 0) return; - // 1ch用一時バッファ(RGBA8パスの1/4サイズ) - uint8_t quadBuffer1ch[CHUNK_SIZE * 4]; // 256 bytes(1byte × 4 × 64) - BilinearWeightXY weightsXY[CHUNK_SIZE]; // 128 bytes - uint8_t edgeFlagsChunk[CHUNK_SIZE]; // 64 bytes + // copyQuadDDA未対応フォーマットは最近傍フォールバック + if (!src.formatID || !src.formatID->copyQuadDDA) { + copyRowDDA(dst, src, count, srcX, srcY, incrX, incrY); + return; + } + + // ======================================================================== + // 1chパス: Alpha8/Grayscale8等の単一チャンネルフォーマット向け高速パス + // フォーマット変換不要、メモリ使用量1/4、演算量約1/4 + // ======================================================================== + if (canUseSingleChannelBilinear(src.formatID, edgeFadeMask)) { + constexpr int CHUNK_SIZE = 64; + + // 1ch用一時バッファ(RGBA8パスの1/4サイズ) + uint8_t quadBuffer1ch[CHUNK_SIZE * 4]; // 256 bytes(1byte × 4 × 64) + BilinearWeightXY weightsXY[CHUNK_SIZE]; // 128 bytes + uint8_t edgeFlagsChunk[CHUNK_SIZE]; // 64 bytes + + uint8_t *dstPtr = static_cast(dst); + const uint8_t *srcData = static_cast(src.data); + + // ViewPortのオフセットを固定小数点に変換して加算 + int_fixed offsetX = static_cast(src.x) << INT_FIXED_SHIFT; + int_fixed offsetY = static_cast(src.y) << INT_FIXED_SHIFT; + + DDAParam param = {src.stride, src.width, src.height, + srcX + offsetX, // オフセット加算 + srcY + offsetY, // オフセット加算 + incrX, incrY, weightsXY, edgeFlagsChunk}; + + for (int_fast16_t offset = 0; offset < count; offset += CHUNK_SIZE) { + int_fast16_t chunk = (count - offset < CHUNK_SIZE) ? (count - offset) : CHUNK_SIZE; + + // 4ピクセル抽出(1 byte/pixel: copyQuadDDA出力がそのまま使える) + src.formatID->copyQuadDDA(quadBuffer1ch, srcData, chunk, ¶m); + + // 境界ピクセルの値を0化(Alpha8のエッジフェード) + if (edgeFadeMask) { + for (int_fast16_t i = 0; i < chunk; ++i) { + uint8_t flags = edgeFlagsChunk[i] & edgeFadeMask; + if (flags) { + uint8_t *q = &quadBuffer1ch[i * 4]; + if (flags & (EdgeFade_Left | EdgeFade_Top)) { + q[0] = 0; + } + if (flags & (EdgeFade_Right | EdgeFade_Top)) { + q[1] = 0; + } + if (flags & (EdgeFade_Left | EdgeFade_Bottom)) { + q[2] = 0; + } + if (flags & (EdgeFade_Right | EdgeFade_Bottom)) { + q[3] = 0; + } + } + } + } + + // 1chバイリニア補間 + bilinearBlend_1ch(dstPtr, quadBuffer1ch, weightsXY, chunk); + + dstPtr += chunk; + param.srcX += incrX * chunk; + param.srcY += incrY * chunk; + } + return; + } + + // ======================================================================== + // RGBA8パス: 通常のマルチチャンネルフォーマット + // ======================================================================== - uint8_t *dstPtr = static_cast(dst); + // チャンク処理用定数 + constexpr int CHUNK_SIZE = 64; + constexpr int RGBA8_BPP = 4; + + // 一時バッファ(スタック確保) + // copyQuadDDA出力とconvertFormat出力を共有(末尾詰め配置でin-place変換可能) + uint32_t quadBuffer[CHUNK_SIZE * 4]; // 1024 bytes(RGBA8888 × 4 × 64) + BilinearWeightXY weightsXY[CHUNK_SIZE]; // 128 bytes (2 * 64) + uint8_t edgeFlagsChunk[CHUNK_SIZE]; // 64 bytes(チャンク用) + + // 元フォーマットのBytesPerPixel(末尾詰め配置用) + // bit-packedの場合、copyQuadDDAはIndex8形式で出力するため、1バイトとする + const int srcBytesPerPixel = (src.formatID->pixelsPerUnit > 1) ? 1 // bit-packed: copyQuadDDAがIndex8で出力 + : src.formatID->bytesPerPixel; // 通常フォーマット + + // フォーマット変換が必要な場合、ループ外で一度だけresolveConverter呼び出し + // bit-packedの場合、copyQuadDDAの出力はIndex8形式なので、Index8→RGBA8の変換を使う + FormatConverter converter; + PixelFormatID converterSrcFormat = (src.formatID->pixelsPerUnit > 1) ? PixelFormatIDs::Index8 : src.formatID; + if (converterSrcFormat != PixelFormatIDs::RGBA8_Straight) { + converter = resolveConverter(converterSrcFormat, PixelFormatIDs::RGBA8_Straight, srcAux); + } + + uint32_t *dstPtr = static_cast(dst); const uint8_t *srcData = static_cast(src.data); // ViewPortのオフセットを固定小数点に変換して加算 int_fixed offsetX = static_cast(src.x) << INT_FIXED_SHIFT; int_fixed offsetY = static_cast(src.y) << INT_FIXED_SHIFT; + // DDAParam を構築 DDAParam param = {src.stride, src.width, src.height, - srcX + offsetX, // オフセット加算 - srcY + offsetY, // オフセット加算 + srcX + offsetX, // オフセット加算 + srcY + offsetY, // オフセット加算 incrX, incrY, weightsXY, edgeFlagsChunk}; for (int_fast16_t offset = 0; offset < count; offset += CHUNK_SIZE) { - int_fast16_t chunk = - (count - offset < CHUNK_SIZE) ? (count - offset) : CHUNK_SIZE; - - // 4ピクセル抽出(1 byte/pixel: copyQuadDDA出力がそのまま使える) - src.formatID->copyQuadDDA(quadBuffer1ch, srcData, chunk, ¶m); - - // 境界ピクセルの値を0化(Alpha8のエッジフェード) - if (edgeFadeMask) { - for (int_fast16_t i = 0; i < chunk; ++i) { - uint8_t flags = edgeFlagsChunk[i] & edgeFadeMask; - if (flags) { - uint8_t *q = &quadBuffer1ch[i * 4]; - if (flags & (EdgeFade_Left | EdgeFade_Top)) { - q[0] = 0; - } - if (flags & (EdgeFade_Right | EdgeFade_Top)) { - q[1] = 0; - } - if (flags & (EdgeFade_Left | EdgeFade_Bottom)) { - q[2] = 0; - } - if (flags & (EdgeFade_Right | EdgeFade_Bottom)) { - q[3] = 0; + int_fast16_t chunk = (count - offset < CHUNK_SIZE) ? (count - offset) : CHUNK_SIZE; + + // 4ピクセル抽出 + edgeFlags生成(末尾詰め配置でin-place変換可能) + int srcQuadSize = srcBytesPerPixel * 4 * chunk; + int dstQuadSize = RGBA8_BPP * 4 * chunk; + auto quadPtr = reinterpret_cast(quadBuffer) + (dstQuadSize - srcQuadSize); + src.formatID->copyQuadDDA(quadPtr, srcData, chunk, ¶m); + + // フォーマット変換(必要な場合、in-place) + if (converter) { + converter(quadBuffer, quadPtr, chunk * 4); + } + uint32_t *quadRGBA = quadBuffer; + + // 境界ピクセルのアルファ0化(edgeFlagsに基づく) + if (edgeFadeMask) { + auto quad = reinterpret_cast(quadRGBA) + 3; + for (int_fast16_t i = 0; i < chunk; ++i) { + uint8_t flags = edgeFlagsChunk[i] & edgeFadeMask; + if (flags) { + if (flags & (EdgeFade_Left | EdgeFade_Top)) { + quad[0] = 0; + } + if (flags & (EdgeFade_Right | EdgeFade_Top)) { + quad[4] = 0; + } + if (flags & (EdgeFade_Left | EdgeFade_Bottom)) { + quad[8] = 0; + } + if (flags & (EdgeFade_Right | EdgeFade_Bottom)) { + quad[12] = 0; + } + } + quad += 4 * 4; } - } } - } - // 1chバイリニア補間 - bilinearBlend_1ch(dstPtr, quadBuffer1ch, weightsXY, chunk); + // バイリニア補間 + bilinearBlend_RGBA8888(dstPtr, quadRGBA, weightsXY, chunk); - dstPtr += chunk; - param.srcX += incrX * chunk; - param.srcY += incrY * chunk; - } - return; - } - - // ======================================================================== - // RGBA8パス: 通常のマルチチャンネルフォーマット - // ======================================================================== - - // チャンク処理用定数 - constexpr int CHUNK_SIZE = 64; - constexpr int RGBA8_BPP = 4; - - // 一時バッファ(スタック確保) - // copyQuadDDA出力とconvertFormat出力を共有(末尾詰め配置でin-place変換可能) - uint32_t quadBuffer[CHUNK_SIZE * 4]; // 1024 bytes(RGBA8888 × 4 × 64) - BilinearWeightXY weightsXY[CHUNK_SIZE]; // 128 bytes (2 * 64) - uint8_t edgeFlagsChunk[CHUNK_SIZE]; // 64 bytes(チャンク用) - - // 元フォーマットのBytesPerPixel(末尾詰め配置用) - // bit-packedの場合、copyQuadDDAはIndex8形式で出力するため、1バイトとする - const int srcBytesPerPixel = - (src.formatID->pixelsPerUnit > 1) - ? 1 // bit-packed: copyQuadDDAがIndex8で出力 - : src.formatID->bytesPerPixel; // 通常フォーマット - - // フォーマット変換が必要な場合、ループ外で一度だけresolveConverter呼び出し - // bit-packedの場合、copyQuadDDAの出力はIndex8形式なので、Index8→RGBA8の変換を使う - FormatConverter converter; - PixelFormatID converterSrcFormat = - (src.formatID->pixelsPerUnit > 1) ? PixelFormatIDs::Index8 : src.formatID; - if (converterSrcFormat != PixelFormatIDs::RGBA8_Straight) { - converter = resolveConverter(converterSrcFormat, - PixelFormatIDs::RGBA8_Straight, srcAux); - } - - uint32_t *dstPtr = static_cast(dst); - const uint8_t *srcData = static_cast(src.data); - - // ViewPortのオフセットを固定小数点に変換して加算 - int_fixed offsetX = static_cast(src.x) << INT_FIXED_SHIFT; - int_fixed offsetY = static_cast(src.y) << INT_FIXED_SHIFT; - - // DDAParam を構築 - DDAParam param = {src.stride, src.width, src.height, - srcX + offsetX, // オフセット加算 - srcY + offsetY, // オフセット加算 - incrX, incrY, weightsXY, edgeFlagsChunk}; - - for (int_fast16_t offset = 0; offset < count; offset += CHUNK_SIZE) { - int_fast16_t chunk = - (count - offset < CHUNK_SIZE) ? (count - offset) : CHUNK_SIZE; - - // 4ピクセル抽出 + edgeFlags生成(末尾詰め配置でin-place変換可能) - int srcQuadSize = srcBytesPerPixel * 4 * chunk; - int dstQuadSize = RGBA8_BPP * 4 * chunk; - auto quadPtr = - reinterpret_cast(quadBuffer) + (dstQuadSize - srcQuadSize); - src.formatID->copyQuadDDA(quadPtr, srcData, chunk, ¶m); - - // フォーマット変換(必要な場合、in-place) - if (converter) { - converter(quadBuffer, quadPtr, chunk * 4); - } - uint32_t *quadRGBA = quadBuffer; - - // 境界ピクセルのアルファ0化(edgeFlagsに基づく) - if (edgeFadeMask) { - auto quad = reinterpret_cast(quadRGBA) + 3; - for (int_fast16_t i = 0; i < chunk; ++i) { - uint8_t flags = edgeFlagsChunk[i] & edgeFadeMask; - if (flags) { - if (flags & (EdgeFade_Left | EdgeFade_Top)) { - quad[0] = 0; - } - if (flags & (EdgeFade_Right | EdgeFade_Top)) { - quad[4] = 0; - } - if (flags & (EdgeFade_Left | EdgeFade_Bottom)) { - quad[8] = 0; - } - if (flags & (EdgeFade_Right | EdgeFade_Bottom)) { - quad[12] = 0; - } - } - quad += 4 * 4; - } + // 次のチャンクへ + dstPtr += chunk; + param.srcX += incrX * chunk; + param.srcY += incrY * chunk; } +} - // バイリニア補間 - bilinearBlend_RGBA8888(dstPtr, quadRGBA, weightsXY, chunk); +void affineTransform(ViewPort &dst, const ViewPort &src, int_fixed invTx, int_fixed invTy, + const Matrix2x2_fixed &invMatrix, int_fixed rowOffsetX, int_fixed rowOffsetY, int_fixed dxOffsetX, + int_fixed dxOffsetY) +{ + if (!dst.isValid() || !src.isValid()) return; + if (!invMatrix.valid) return; - // 次のチャンクへ - dstPtr += chunk; - param.srcX += incrX * chunk; - param.srcY += incrY * chunk; - } -} + const int outW = dst.width; + const int outH = dst.height; -void affineTransform(ViewPort &dst, const ViewPort &src, int_fixed invTx, - int_fixed invTy, const Matrix2x2_fixed &invMatrix, - int_fixed rowOffsetX, int_fixed rowOffsetY, - int_fixed dxOffsetX, int_fixed dxOffsetY) { - if (!dst.isValid() || !src.isValid()) - return; - if (!invMatrix.valid) - return; - - const int outW = dst.width; - const int outH = dst.height; - - const int_fixed incrX = invMatrix.a; - const int_fixed incrY = invMatrix.c; - const int_fixed invB = invMatrix.b; - const int_fixed invD = invMatrix.d; - - for (int dy = 0; dy < outH; dy++) { - int_fixed rowBaseX = invB * dy + invTx + rowOffsetX; - int_fixed rowBaseY = invD * dy + invTy + rowOffsetY; - - auto [xStart, xEnd] = - transform::calcValidRange(incrX, rowBaseX, src.width, outW); - auto [yStart, yEnd] = - transform::calcValidRange(incrY, rowBaseY, src.height, outW); - int dxStart = std::max({0, xStart, yStart}); - int dxEnd = std::min({outW - 1, xEnd, yEnd}); - - if (dxStart > dxEnd) - continue; - - int_fixed srcX = incrX * dxStart + rowBaseX + dxOffsetX; - int_fixed srcY = incrY * dxStart + rowBaseY + dxOffsetY; - int_fast16_t count = static_cast(dxEnd - dxStart + 1); - - void *dstRow = dst.pixelAt(dxStart, dy); - - copyRowDDA(dstRow, src, count, srcX, srcY, incrX, incrY); - } + const int_fixed incrX = invMatrix.a; + const int_fixed incrY = invMatrix.c; + const int_fixed invB = invMatrix.b; + const int_fixed invD = invMatrix.d; + + for (int dy = 0; dy < outH; dy++) { + int_fixed rowBaseX = invB * dy + invTx + rowOffsetX; + int_fixed rowBaseY = invD * dy + invTy + rowOffsetY; + + auto [xStart, xEnd] = transform::calcValidRange(incrX, rowBaseX, src.width, outW); + auto [yStart, yEnd] = transform::calcValidRange(incrY, rowBaseY, src.height, outW); + int dxStart = std::max({0, xStart, yStart}); + int dxEnd = std::min({outW - 1, xEnd, yEnd}); + + if (dxStart > dxEnd) continue; + + int_fixed srcX = incrX * dxStart + rowBaseX + dxOffsetX; + int_fixed srcY = incrY * dxStart + rowBaseY + dxOffsetY; + int_fast16_t count = static_cast(dxEnd - dxStart + 1); + + void *dstRow = dst.pixelAt(dxStart, dy); + + copyRowDDA(dstRow, src, count, srcX, srcY, incrX, incrY); + } } -} // namespace view_ops -} // namespace FLEXIMG_NAMESPACE +} // namespace view_ops +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_VIEWPORT_H +#endif // FLEXIMG_VIEWPORT_H diff --git a/src/fleximg/nodes/affine_node.h b/src/fleximg/nodes/affine_node.h index 43b05fc..5aa9dd9 100644 --- a/src/fleximg/nodes/affine_node.h +++ b/src/fleximg/nodes/affine_node.h @@ -32,38 +32,44 @@ namespace FLEXIMG_NAMESPACE { class AffineNode : public Node, public AffineCapability { public: - AffineNode() { - initPorts(1, 1); // 入力1、出力1 - } + AffineNode() + { + initPorts(1, 1); // 入力1、出力1 + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "AffineNode"; } + const char *name() const override + { + return "AffineNode"; + } protected: - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPullPrepare: アフィン行列を上流に伝播し、SourceNodeで一括実行 - PrepareResponse onPullPrepare(const PrepareRequest &request) override; + // onPullPrepare: アフィン行列を上流に伝播し、SourceNodeで一括実行 + PrepareResponse onPullPrepare(const PrepareRequest &request) override; - // onPushPrepare: アフィン行列を下流に伝播し、SinkNodeで一括実行 - PrepareResponse onPushPrepare(const PrepareRequest &request) override; + // onPushPrepare: アフィン行列を下流に伝播し、SinkNodeで一括実行 + PrepareResponse onPushPrepare(const PrepareRequest &request) override; - // onPullProcess: AffineNodeは行列を保持するのみ、パススルー - RenderResponse &onPullProcess(const RenderRequest &request) override; + // onPullProcess: AffineNodeは行列を保持するのみ、パススルー + RenderResponse &onPullProcess(const RenderRequest &request) override; - // onPushProcess: AffineNodeは行列を保持するのみ、パススルー - void onPushProcess(RenderResponse &input, - const RenderRequest &request) override; + // onPushProcess: AffineNodeは行列を保持するのみ、パススルー + void onPushProcess(RenderResponse &input, const RenderRequest &request) override; - int nodeTypeForMetrics() const override { return NodeType::Affine; } + int nodeTypeForMetrics() const override + { + return NodeType::Affine; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -77,74 +83,76 @@ namespace FLEXIMG_NAMESPACE { // ============================================================================ // 複数のAffineNodeがある場合は行列を合成する -PrepareResponse AffineNode::onPullPrepare(const PrepareRequest &request) { - // 上流に渡すためのコピーを作成し、自身の行列を累積 - PrepareRequest upstreamRequest = request; - if (upstreamRequest.hasAffine) { - // 既存の行列(下流側)に自身の行列(上流側)を後から掛ける - upstreamRequest.affineMatrix = upstreamRequest.affineMatrix * localMatrix_; - } else { - upstreamRequest.affineMatrix = localMatrix_; - upstreamRequest.hasAffine = true; - } - - // 上流へ伝播 - Node *upstream = upstreamNode(0); - if (upstream) { - return upstream->pullPrepare(upstreamRequest); // パススルー - } - // 上流なし: 有効なデータがないのでサイズ0を返す - PrepareResponse result; - result.status = PrepareStatus::Prepared; - // width/height/originはデフォルト値(0)のまま - return result; +PrepareResponse AffineNode::onPullPrepare(const PrepareRequest &request) +{ + // 上流に渡すためのコピーを作成し、自身の行列を累積 + PrepareRequest upstreamRequest = request; + if (upstreamRequest.hasAffine) { + // 既存の行列(下流側)に自身の行列(上流側)を後から掛ける + upstreamRequest.affineMatrix = upstreamRequest.affineMatrix * localMatrix_; + } else { + upstreamRequest.affineMatrix = localMatrix_; + upstreamRequest.hasAffine = true; + } + + // 上流へ伝播 + Node *upstream = upstreamNode(0); + if (upstream) { + return upstream->pullPrepare(upstreamRequest); // パススルー + } + // 上流なし: 有効なデータがないのでサイズ0を返す + PrepareResponse result; + result.status = PrepareStatus::Prepared; + // width/height/originはデフォルト値(0)のまま + return result; } // 複数のAffineNodeがある場合は行列を合成する -PrepareResponse AffineNode::onPushPrepare(const PrepareRequest &request) { - // 下流に渡すためのコピーを作成し、自身の行列を累積 - PrepareRequest downstreamRequest = request; - if (downstreamRequest.hasPushAffine) { - // Pull側と同じ合成順序にするため、自身の行列を先に掛ける - // Pull側: M2 * M1(下流から上流へ伝播、後から掛ける) - // Push側: M2 * M1(上流から下流へ伝播、先に掛ける) - downstreamRequest.pushAffineMatrix = - localMatrix_ * downstreamRequest.pushAffineMatrix; - } else { - downstreamRequest.pushAffineMatrix = localMatrix_; - downstreamRequest.hasPushAffine = true; - } - - // 下流へ伝播 - Node *downstream = downstreamNode(0); - if (downstream) { - return downstream->pushPrepare(downstreamRequest); // パススルー - } - // 下流なし: 有効なデータがないのでサイズ0を返す - PrepareResponse result; - result.status = PrepareStatus::Prepared; - // width/height/originはデフォルト値(0)のまま - return result; +PrepareResponse AffineNode::onPushPrepare(const PrepareRequest &request) +{ + // 下流に渡すためのコピーを作成し、自身の行列を累積 + PrepareRequest downstreamRequest = request; + if (downstreamRequest.hasPushAffine) { + // Pull側と同じ合成順序にするため、自身の行列を先に掛ける + // Pull側: M2 * M1(下流から上流へ伝播、後から掛ける) + // Push側: M2 * M1(上流から下流へ伝播、先に掛ける) + downstreamRequest.pushAffineMatrix = localMatrix_ * downstreamRequest.pushAffineMatrix; + } else { + downstreamRequest.pushAffineMatrix = localMatrix_; + downstreamRequest.hasPushAffine = true; + } + + // 下流へ伝播 + Node *downstream = downstreamNode(0); + if (downstream) { + return downstream->pushPrepare(downstreamRequest); // パススルー + } + // 下流なし: 有効なデータがないのでサイズ0を返す + PrepareResponse result; + result.status = PrepareStatus::Prepared; + // width/height/originはデフォルト値(0)のまま + return result; } -RenderResponse &AffineNode::onPullProcess(const RenderRequest &request) { - Node *upstream = upstreamNode(0); - if (upstream) { - return upstream->pullProcess(request); - } - return makeEmptyResponse(request.origin); +RenderResponse &AffineNode::onPullProcess(const RenderRequest &request) +{ + Node *upstream = upstreamNode(0); + if (upstream) { + return upstream->pullProcess(request); + } + return makeEmptyResponse(request.origin); } -void AffineNode::onPushProcess(RenderResponse &input, - const RenderRequest &request) { - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushProcess(input, request); - } +void AffineNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(input, request); + } } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_AFFINE_NODE_H +#endif // FLEXIMG_AFFINE_NODE_H diff --git a/src/fleximg/nodes/alpha_node.h b/src/fleximg/nodes/alpha_node.h index 9e0416d..341e2ea 100644 --- a/src/fleximg/nodes/alpha_node.h +++ b/src/fleximg/nodes/alpha_node.h @@ -20,28 +20,44 @@ namespace FLEXIMG_NAMESPACE { class AlphaNode : public FilterNodeBase { public: - AlphaNode() { params_.value1 = 1.0f; } // デフォルト: 変化なし - - // ======================================== - // パラメータ設定 - // ======================================== - - void setScale(float scale) { params_.value1 = scale; } - float scale() const { return params_.value1; } - - // ======================================== - // Node インターフェース - // ======================================== - - const char *name() const override { return "AlphaNode"; } + AlphaNode() + { + params_.value1 = 1.0f; + } // デフォルト: 変化なし + + // ======================================== + // パラメータ設定 + // ======================================== + + void setScale(float scale) + { + params_.value1 = scale; + } + float scale() const + { + return params_.value1; + } + + // ======================================== + // Node インターフェース + // ======================================== + + const char *name() const override + { + return "AlphaNode"; + } protected: - filters::LineFilterFunc getFilterFunc() const override { - return &filters::alpha_line; - } - int nodeTypeForMetrics() const override { return NodeType::Alpha; } + filters::LineFilterFunc getFilterFunc() const override + { + return &filters::alpha_line; + } + int nodeTypeForMetrics() const override + { + return NodeType::Alpha; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_ALPHA_NODE_H +#endif // FLEXIMG_ALPHA_NODE_H diff --git a/src/fleximg/nodes/brightness_node.h b/src/fleximg/nodes/brightness_node.h index 32aa0d4..9de40f5 100644 --- a/src/fleximg/nodes/brightness_node.h +++ b/src/fleximg/nodes/brightness_node.h @@ -20,26 +20,39 @@ namespace FLEXIMG_NAMESPACE { class BrightnessNode : public FilterNodeBase { public: - // ======================================== - // パラメータ設定 - // ======================================== - - void setAmount(float amount) { params_.value1 = amount; } - float amount() const { return params_.value1; } - - // ======================================== - // Node インターフェース - // ======================================== - - const char *name() const override { return "BrightnessNode"; } + // ======================================== + // パラメータ設定 + // ======================================== + + void setAmount(float amount) + { + params_.value1 = amount; + } + float amount() const + { + return params_.value1; + } + + // ======================================== + // Node インターフェース + // ======================================== + + const char *name() const override + { + return "BrightnessNode"; + } protected: - filters::LineFilterFunc getFilterFunc() const override { - return &filters::brightness_line; - } - int nodeTypeForMetrics() const override { return NodeType::Brightness; } + filters::LineFilterFunc getFilterFunc() const override + { + return &filters::brightness_line; + } + int nodeTypeForMetrics() const override + { + return NodeType::Brightness; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_BRIGHTNESS_NODE_H +#endif // FLEXIMG_BRIGHTNESS_NODE_H diff --git a/src/fleximg/nodes/composite_node.h b/src/fleximg/nodes/composite_node.h index b06f642..397ea8d 100644 --- a/src/fleximg/nodes/composite_node.h +++ b/src/fleximg/nodes/composite_node.h @@ -43,61 +43,69 @@ namespace FLEXIMG_NAMESPACE { class CompositeNode : public Node, public AffineCapability { public: - explicit CompositeNode(int_fast16_t inputCount = 2) { - initPorts(inputCount, 1); // 入力N、出力1 - } - - // ======================================== - // 入力管理 - // ======================================== - - // 入力数を変更(既存接続は維持) - void setInputCount(int_fast16_t count) { - if (count < 1) - count = 1; - inputs_.resize(static_cast(count)); - for (int_fast16_t i = 0; i < count; ++i) { - if (inputs_[static_cast(i)].owner == nullptr) { - inputs_[static_cast(i)] = core::Port(this, static_cast(i)); - } + explicit CompositeNode(int_fast16_t inputCount = 2) + { + initPorts(inputCount, 1); // 入力N、出力1 } - } - int_fast16_t inputCount() const { - return static_cast(inputs_.size()); - } + // ======================================== + // 入力管理 + // ======================================== + + // 入力数を変更(既存接続は維持) + void setInputCount(int_fast16_t count) + { + if (count < 1) count = 1; + inputs_.resize(static_cast(count)); + for (int_fast16_t i = 0; i < count; ++i) { + if (inputs_[static_cast(i)].owner == nullptr) { + inputs_[static_cast(i)] = core::Port(this, static_cast(i)); + } + } + } + + int_fast16_t inputCount() const + { + return static_cast(inputs_.size()); + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "CompositeNode"; } + const char *name() const override + { + return "CompositeNode"; + } - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPullPrepare: 全上流ノードにPrepareRequestを伝播 - PrepareResponse onPullPrepare(const PrepareRequest &request) override; + // onPullPrepare: 全上流ノードにPrepareRequestを伝播 + PrepareResponse onPullPrepare(const PrepareRequest &request) override; - // onPullFinalize: 全上流ノードに終了を伝播 - void onPullFinalize() override; + // onPullFinalize: 全上流ノードに終了を伝播 + void onPullFinalize() override; - // onPullProcess: 複数の上流から画像を取得してunder合成 - RenderResponse &onPullProcess(const RenderRequest &request) override; + // onPullProcess: 複数の上流から画像を取得してunder合成 + RenderResponse &onPullProcess(const RenderRequest &request) override; - // getDataRange: 全上流のgetDataRange和集合を返す - DataRange getDataRange(const RenderRequest &request) const override; + // getDataRange: 全上流のgetDataRange和集合を返す + DataRange getDataRange(const RenderRequest &request) const override; protected: - int nodeTypeForMetrics() const override { return NodeType::Composite; } + int nodeTypeForMetrics() const override + { + return NodeType::Composite; + } private: - // getDataRangeキャッシュ(同一スキャンラインでの重複計算を回避) - mutable core::DataRangeCache dataRangeCache_; + // getDataRangeキャッシュ(同一スキャンラインでの重複計算を回避) + mutable core::DataRangeCache dataRangeCache_; }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -110,150 +118,143 @@ namespace FLEXIMG_NAMESPACE { // CompositeNode - Template Method フック実装 // ============================================================================ -PrepareResponse CompositeNode::onPullPrepare(const PrepareRequest &request) { - PrepareResponse merged; - merged.status = PrepareStatus::Prepared; - int validUpstreamCount = 0; - - // AABB和集合計算用(基準点からの相対座標) - float minX = 0, minY = 0, maxX = 0, maxY = 0; - - // 上流に渡すリクエストを作成(localMatrix_ を累積) - PrepareRequest upstreamRequest = request; - if (hasLocalTransform()) { - // 行列合成: request.affineMatrix * localMatrix_ - // AffineNode直列接続と同じ解釈順序 - if (upstreamRequest.hasAffine) { - upstreamRequest.affineMatrix = - upstreamRequest.affineMatrix * localMatrix_; - } else { - upstreamRequest.affineMatrix = localMatrix_; - upstreamRequest.hasAffine = true; +PrepareResponse CompositeNode::onPullPrepare(const PrepareRequest &request) +{ + PrepareResponse merged; + merged.status = PrepareStatus::Prepared; + int validUpstreamCount = 0; + + // AABB和集合計算用(基準点からの相対座標) + float minX = 0, minY = 0, maxX = 0, maxY = 0; + + // 上流に渡すリクエストを作成(localMatrix_ を累積) + PrepareRequest upstreamRequest = request; + if (hasLocalTransform()) { + // 行列合成: request.affineMatrix * localMatrix_ + // AffineNode直列接続と同じ解釈順序 + if (upstreamRequest.hasAffine) { + upstreamRequest.affineMatrix = upstreamRequest.affineMatrix * localMatrix_; + } else { + upstreamRequest.affineMatrix = localMatrix_; + upstreamRequest.hasAffine = true; + } } - } - - // 全上流へ伝播し、結果をマージ(AABB和集合) - auto numInputs = inputCount(); - for (int_fast16_t i = 0; i < numInputs; ++i) { - Node *upstream = upstreamNode(i); - if (upstream) { - // 各上流に同じリクエストを伝播 - // 注意: アフィン行列は共有されるため、各上流で同じ変換が適用される - PrepareResponse result = upstream->pullPrepare(upstreamRequest); - if (!result.ok()) { - return result; // エラーを伝播 - } - - // 各結果のAABBをワールド座標に変換 - // origin はバッファ左上のワールド座標 - float left = fixed_to_float(result.origin.x); - float top = fixed_to_float(result.origin.y); - float right = left + static_cast(result.width); - float bottom = top + static_cast(result.height); - - if (validUpstreamCount == 0) { - // 最初の結果でベースを初期化 - merged.preferredFormat = result.preferredFormat; - minX = left; - minY = top; - maxX = right; - maxY = bottom; - } else { - // 和集合(各辺のmin/max) - if (left < minX) - minX = left; - if (top < minY) - minY = top; - if (right > maxX) - maxX = right; - if (bottom > maxY) - maxY = bottom; - } - ++validUpstreamCount; + + // 全上流へ伝播し、結果をマージ(AABB和集合) + auto numInputs = inputCount(); + for (int_fast16_t i = 0; i < numInputs; ++i) { + Node *upstream = upstreamNode(i); + if (upstream) { + // 各上流に同じリクエストを伝播 + // 注意: アフィン行列は共有されるため、各上流で同じ変換が適用される + PrepareResponse result = upstream->pullPrepare(upstreamRequest); + if (!result.ok()) { + return result; // エラーを伝播 + } + + // 各結果のAABBをワールド座標に変換 + // origin はバッファ左上のワールド座標 + float left = fixed_to_float(result.origin.x); + float top = fixed_to_float(result.origin.y); + float right = left + static_cast(result.width); + float bottom = top + static_cast(result.height); + + if (validUpstreamCount == 0) { + // 最初の結果でベースを初期化 + merged.preferredFormat = result.preferredFormat; + minX = left; + minY = top; + maxX = right; + maxY = bottom; + } else { + // 和集合(各辺のmin/max) + if (left < minX) minX = left; + if (top < minY) minY = top; + if (right > maxX) maxX = right; + if (bottom > maxY) maxY = bottom; + } + ++validUpstreamCount; + } } - } - - if (validUpstreamCount > 0) { - // 和集合結果をPrepareResponseに設定 - // origin はバッファ左上のワールド座標 - merged.width = static_cast(std::ceil(maxX - minX)); - merged.height = static_cast(std::ceil(maxY - minY)); - merged.origin.x = float_to_fixed(minX); - merged.origin.y = float_to_fixed(minY); - - // フォーマット決定: - // - 上流が1つのみ → パススルー(merged.preferredFormatはそのまま) - // - 上流が複数 → 合成フォーマットを使用 - if (validUpstreamCount > 1) { - merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; + + if (validUpstreamCount > 0) { + // 和集合結果をPrepareResponseに設定 + // origin はバッファ左上のワールド座標 + merged.width = static_cast(std::ceil(maxX - minX)); + merged.height = static_cast(std::ceil(maxY - minY)); + merged.origin.x = float_to_fixed(minX); + merged.origin.y = float_to_fixed(minY); + + // フォーマット決定: + // - 上流が1つのみ → パススルー(merged.preferredFormatはそのまま) + // - 上流が複数 → 合成フォーマットを使用 + if (validUpstreamCount > 1) { + merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; + } + } else { + // 上流がない場合はサイズ0を返す + // width/height/originはデフォルト値(0)のまま } - } else { - // 上流がない場合はサイズ0を返す - // width/height/originはデフォルト値(0)のまま - } - - // 準備処理 - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - - // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) - dataRangeCache_.invalidate(); - - return merged; + + // 準備処理 + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + + // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) + dataRangeCache_.invalidate(); + + return merged; } -void CompositeNode::onPullFinalize() { - finalize(); - auto numInputs = inputCount(); - for (int_fast16_t i = 0; i < numInputs; ++i) { - Node *upstream = upstreamNode(i); - if (upstream) { - upstream->pullFinalize(); +void CompositeNode::onPullFinalize() +{ + finalize(); + auto numInputs = inputCount(); + for (int_fast16_t i = 0; i < numInputs; ++i) { + Node *upstream = upstreamNode(i); + if (upstream) { + upstream->pullFinalize(); + } } - } } // getDataRange: 全上流のgetDataRange和集合を返す // 同一スキャンラインでの重複呼び出しはキャッシュで高速化 -DataRange CompositeNode::getDataRange(const RenderRequest &request) const { - // キャッシュヒットチェック - DataRange cached; - if (dataRangeCache_.tryGet(request, cached)) { - return cached; - } - - auto numInputs = inputCount(); - int_fast16_t startX = request.width; // 右端で初期化 - int_fast16_t endX = 0; // 左端で初期化 - - for (int_fast16_t i = 0; i < numInputs; ++i) { - Node *upstream = upstreamNode(i); - if (!upstream) - continue; - - DataRange range = upstream->getDataRange(request); - if (!range.hasData()) - continue; - - // 和集合を更新 - if (range.startX < startX) - startX = range.startX; - if (range.endX > endX) - endX = range.endX; - } - - // startX >= endX はデータなし - DataRange result = (startX < endX) ? DataRange{static_cast(startX), - static_cast(endX)} - : DataRange{0, 0}; - - // キャッシュ更新 - dataRangeCache_.set(request, result); - - return result; +DataRange CompositeNode::getDataRange(const RenderRequest &request) const +{ + // キャッシュヒットチェック + DataRange cached; + if (dataRangeCache_.tryGet(request, cached)) { + return cached; + } + + auto numInputs = inputCount(); + int_fast16_t startX = request.width; // 右端で初期化 + int_fast16_t endX = 0; // 左端で初期化 + + for (int_fast16_t i = 0; i < numInputs; ++i) { + Node *upstream = upstreamNode(i); + if (!upstream) continue; + + DataRange range = upstream->getDataRange(request); + if (!range.hasData()) continue; + + // 和集合を更新 + if (range.startX < startX) startX = range.startX; + if (range.endX > endX) endX = range.endX; + } + + // startX >= endX はデータなし + DataRange result = + (startX < endX) ? DataRange{static_cast(startX), static_cast(endX)} : DataRange{0, 0}; + + // キャッシュ更新 + dataRangeCache_.set(request, result); + + return result; } // onPullProcess: 複数の上流から画像を取得してunder合成 @@ -261,58 +262,55 @@ DataRange CompositeNode::getDataRange(const RenderRequest &request) const { // - getDataRangeで合成範囲を事前計算 // - hintRangeサイズの合成バッファをゼロ初期化で確保 // - 各上流の結果をblendFromで直接書き込み -RenderResponse &CompositeNode::onPullProcess(const RenderRequest &request) { - auto numInputs = inputCount(); - if (numInputs == 0) - return makeEmptyResponse(request.origin); - - // 1. hintRange取得(キャッシュ付き) - DataRange hintRange = getDataRange(request); - if (!hintRange.hasData()) - return makeEmptyResponse(request.origin); - - // 2. 合成バッファ確保(ゼロ初期化) - int16_t hintWidth = static_cast(hintRange.endX - hintRange.startX); - Point compositeOrigin = request.origin; - compositeOrigin.x += to_fixed(hintRange.startX); - - RenderResponse &resp = context_->acquireResponse(); - ImageBuffer *compositeBuf = resp.createBuffer( - hintWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero); - - if (!compositeBuf || !compositeBuf->isValid()) { - return resp; // alloc失敗 - } - compositeBuf->setOrigin(compositeOrigin); - - // 3. 各上流を処理 - for (int_fast16_t i = 0; i < numInputs; ++i) { - Node *upstream = upstreamNode(i); - if (!upstream) - continue; - - RenderResponse &input = upstream->pullProcess(request); - if (!input.isValid()) { - context_->releaseResponse(input); - continue; - } +RenderResponse &CompositeNode::onPullProcess(const RenderRequest &request) +{ + auto numInputs = inputCount(); + if (numInputs == 0) return makeEmptyResponse(request.origin); - FLEXIMG_METRICS_SCOPE(NodeType::Composite); + // 1. hintRange取得(キャッシュ付き) + DataRange hintRange = getDataRange(request); + if (!hintRange.hasData()) return makeEmptyResponse(request.origin); - // 上流のバッファをblendFrom - if (input.hasBuffer()) { - compositeBuf->blendFrom(input.buffer()); + // 2. 合成バッファ確保(ゼロ初期化) + int16_t hintWidth = static_cast(hintRange.endX - hintRange.startX); + Point compositeOrigin = request.origin; + compositeOrigin.x += to_fixed(hintRange.startX); + + RenderResponse &resp = context_->acquireResponse(); + ImageBuffer *compositeBuf = resp.createBuffer(hintWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero); + + if (!compositeBuf || !compositeBuf->isValid()) { + return resp; // alloc失敗 } + compositeBuf->setOrigin(compositeOrigin); + + // 3. 各上流を処理 + for (int_fast16_t i = 0; i < numInputs; ++i) { + Node *upstream = upstreamNode(i); + if (!upstream) continue; + + RenderResponse &input = upstream->pullProcess(request); + if (!input.isValid()) { + context_->releaseResponse(input); + continue; + } - context_->releaseResponse(input); - } + FLEXIMG_METRICS_SCOPE(NodeType::Composite); + + // 上流のバッファをblendFrom + if (input.hasBuffer()) { + compositeBuf->blendFrom(input.buffer()); + } + + context_->releaseResponse(input); + } - resp.origin = compositeOrigin; - return resp; + resp.origin = compositeOrigin; + return resp; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_COMPOSITE_NODE_H +#endif // FLEXIMG_COMPOSITE_NODE_H diff --git a/src/fleximg/nodes/distributor_node.h b/src/fleximg/nodes/distributor_node.h index b57c31b..386dd06 100644 --- a/src/fleximg/nodes/distributor_node.h +++ b/src/fleximg/nodes/distributor_node.h @@ -38,51 +38,60 @@ namespace FLEXIMG_NAMESPACE { class DistributorNode : public Node, public AffineCapability { public: - explicit DistributorNode(int outputCount = 1) { - initPorts(1, outputCount); // 入力1、出力N - } - - // ======================================== - // 出力管理(CompositeNode::setInputCount と対称) - // ======================================== - - // 出力数を変更(既存接続は維持) - void setOutputCount(int_fast16_t count) { - if (count < 1) - count = 1; - outputs_.resize(static_cast(count)); - for (int i = 0; i < count; ++i) { - if (outputs_[static_cast(i)].owner == nullptr) { - outputs_[static_cast(i)] = core::Port(this, i); - } + explicit DistributorNode(int outputCount = 1) + { + initPorts(1, outputCount); // 入力1、出力N } - } - int outputCount() const { return static_cast(outputs_.size()); } + // ======================================== + // 出力管理(CompositeNode::setInputCount と対称) + // ======================================== + + // 出力数を変更(既存接続は維持) + void setOutputCount(int_fast16_t count) + { + if (count < 1) count = 1; + outputs_.resize(static_cast(count)); + for (int i = 0; i < count; ++i) { + if (outputs_[static_cast(i)].owner == nullptr) { + outputs_[static_cast(i)] = core::Port(this, i); + } + } + } - // ======================================== - // Node インターフェース - // ======================================== + int outputCount() const + { + return static_cast(outputs_.size()); + } - const char *name() const override { return "DistributorNode"; } - int nodeTypeForMetrics() const override { return NodeType::Distributor; } + // ======================================== + // Node インターフェース + // ======================================== + + const char *name() const override + { + return "DistributorNode"; + } + int nodeTypeForMetrics() const override + { + return NodeType::Distributor; + } - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPushPrepare: 全下流ノードにPrepareRequestを伝播 - PrepareResponse onPushPrepare(const PrepareRequest &request) override; + // onPushPrepare: 全下流ノードにPrepareRequestを伝播 + PrepareResponse onPushPrepare(const PrepareRequest &request) override; - // onPushFinalize: 全下流ノードに終了を伝播 - void onPushFinalize() override; + // onPushFinalize: 全下流ノードに終了を伝播 + void onPushFinalize() override; - // onPushProcess: 全出力に参照モードで配信 - void onPushProcess(RenderResponse &input, - const RenderRequest &request) override; + // onPushProcess: 全出力に参照モードで配信 + void onPushProcess(RenderResponse &input, const RenderRequest &request) override; }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -95,161 +104,156 @@ namespace FLEXIMG_NAMESPACE { // DistributorNode - Template Method フック実装 // ============================================================================ -PrepareResponse DistributorNode::onPushPrepare(const PrepareRequest &request) { - // 準備処理 - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - - PrepareResponse merged; - merged.status = PrepareStatus::Prepared; - bool hasValidDownstream = false; - bool formatMismatch = false; - - // AABB和集合計算用(基準点からの相対座標) - float minX = 0, minY = 0, maxX = 0, maxY = 0; - - // 下流に渡すリクエストを作成(localMatrix_ を累積) - PrepareRequest downstreamRequest = request; - if (hasLocalTransform()) { - // 行列合成: request.pushAffineMatrix * localMatrix_ - // AffineNode直列接続と同じ解釈順序 - if (downstreamRequest.hasPushAffine) { - downstreamRequest.pushAffineMatrix = - downstreamRequest.pushAffineMatrix * localMatrix_; - } else { - downstreamRequest.pushAffineMatrix = localMatrix_; - downstreamRequest.hasPushAffine = true; +PrepareResponse DistributorNode::onPushPrepare(const PrepareRequest &request) +{ + // 準備処理 + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + + PrepareResponse merged; + merged.status = PrepareStatus::Prepared; + bool hasValidDownstream = false; + bool formatMismatch = false; + + // AABB和集合計算用(基準点からの相対座標) + float minX = 0, minY = 0, maxX = 0, maxY = 0; + + // 下流に渡すリクエストを作成(localMatrix_ を累積) + PrepareRequest downstreamRequest = request; + if (hasLocalTransform()) { + // 行列合成: request.pushAffineMatrix * localMatrix_ + // AffineNode直列接続と同じ解釈順序 + if (downstreamRequest.hasPushAffine) { + downstreamRequest.pushAffineMatrix = downstreamRequest.pushAffineMatrix * localMatrix_; + } else { + downstreamRequest.pushAffineMatrix = localMatrix_; + downstreamRequest.hasPushAffine = true; + } } - } - - // 全下流へ伝播し、結果をマージ(AABB和集合) - int numOutputs = outputCount(); - for (int i = 0; i < numOutputs; ++i) { - Node *downstream = downstreamNode(i); - if (downstream) { - PrepareResponse result = downstream->pushPrepare(downstreamRequest); - if (!result.ok()) { - return result; // エラーを伝播 - } - - // 各結果のAABBを基準点からの相対座標に変換 - float left = -fixed_to_float(result.origin.x); - float top = -fixed_to_float(result.origin.y); - float right = left + static_cast(result.width); - float bottom = top + static_cast(result.height); - - if (!hasValidDownstream) { - // 最初の結果でベースを初期化 - merged.preferredFormat = result.preferredFormat; - minX = left; - minY = top; - maxX = right; - maxY = bottom; - hasValidDownstream = true; - } else { - // 和集合(各辺のmin/max) - if (left < minX) - minX = left; - if (top < minY) - minY = top; - if (right > maxX) - maxX = right; - if (bottom > maxY) - maxY = bottom; - // フォーマットの差異をチェック - if (merged.preferredFormat != result.preferredFormat) { - formatMismatch = true; + + // 全下流へ伝播し、結果をマージ(AABB和集合) + int numOutputs = outputCount(); + for (int i = 0; i < numOutputs; ++i) { + Node *downstream = downstreamNode(i); + if (downstream) { + PrepareResponse result = downstream->pushPrepare(downstreamRequest); + if (!result.ok()) { + return result; // エラーを伝播 + } + + // 各結果のAABBを基準点からの相対座標に変換 + float left = -fixed_to_float(result.origin.x); + float top = -fixed_to_float(result.origin.y); + float right = left + static_cast(result.width); + float bottom = top + static_cast(result.height); + + if (!hasValidDownstream) { + // 最初の結果でベースを初期化 + merged.preferredFormat = result.preferredFormat; + minX = left; + minY = top; + maxX = right; + maxY = bottom; + hasValidDownstream = true; + } else { + // 和集合(各辺のmin/max) + if (left < minX) minX = left; + if (top < minY) minY = top; + if (right > maxX) maxX = right; + if (bottom > maxY) maxY = bottom; + // フォーマットの差異をチェック + if (merged.preferredFormat != result.preferredFormat) { + formatMismatch = true; + } + } } - } } - } - - if (hasValidDownstream) { - // 和集合結果をPrepareResponseに設定 - merged.width = static_cast(std::ceil(maxX - minX)); - merged.height = static_cast(std::ceil(maxY - minY)); - merged.origin.x = float_to_fixed(-minX); - merged.origin.y = float_to_fixed(-minY); - // フォーマット決定: - // - 全下流が同じフォーマット → そのフォーマットを採用 - // - 下流に差異 → RGBA8_Straightを採用(共通の中間フォーマット) - if (formatMismatch) { - merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; + + if (hasValidDownstream) { + // 和集合結果をPrepareResponseに設定 + merged.width = static_cast(std::ceil(maxX - minX)); + merged.height = static_cast(std::ceil(maxY - minY)); + merged.origin.x = float_to_fixed(-minX); + merged.origin.y = float_to_fixed(-minY); + // フォーマット決定: + // - 全下流が同じフォーマット → そのフォーマットを採用 + // - 下流に差異 → RGBA8_Straightを採用(共通の中間フォーマット) + if (formatMismatch) { + merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; + } + } else { + // 下流がない場合はサイズ0を返す + // width/height/originはデフォルト値(0)のまま } - } else { - // 下流がない場合はサイズ0を返す - // width/height/originはデフォルト値(0)のまま - } - return merged; + return merged; } -void DistributorNode::onPushFinalize() { - // 全下流へ伝播 - int numOutputs = outputCount(); - for (int i = 0; i < numOutputs; ++i) { - Node *downstream = downstreamNode(i); - if (downstream) { - downstream->pushFinalize(); +void DistributorNode::onPushFinalize() +{ + // 全下流へ伝播 + int numOutputs = outputCount(); + for (int i = 0; i < numOutputs; ++i) { + Node *downstream = downstreamNode(i); + if (downstream) { + downstream->pushFinalize(); + } } - } - finalize(); + finalize(); } -void DistributorNode::onPushProcess(RenderResponse &input, - const RenderRequest &request) { - // プッシュ型単一入力: 無効なら処理終了 - if (!input.isValid()) { - return; - } +void DistributorNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + // プッシュ型単一入力: 無効なら処理終了 + if (!input.isValid()) { + return; + } + + // バッファ準備 + consolidateIfNeeded(input); - // バッファ準備 - consolidateIfNeeded(input); + FLEXIMG_METRICS_SCOPE(NodeType::Distributor); - FLEXIMG_METRICS_SCOPE(NodeType::Distributor); + int numOutputs = outputCount(); + int validOutputs = 0; - int numOutputs = outputCount(); - int validOutputs = 0; + // 接続されている出力を数える + for (int i = 0; i < numOutputs; ++i) { + if (downstreamNode(i)) { + ++validOutputs; + } + } - // 接続されている出力を数える - for (int i = 0; i < numOutputs; ++i) { - if (downstreamNode(i)) { - ++validOutputs; + if (validOutputs == 0) { + return; } - } - - if (validOutputs == 0) { - return; - } - - // 各出力に参照モードImageBufferを配信 - int processed = 0; - for (int i = 0; i < numOutputs; ++i) { - Node *downstream = downstreamNode(i); - if (!downstream) - continue; - - ++processed; - - // 参照モードImageBufferを作成(メモリ解放しない) - // 最後の出力には元のバッファの参照をそのまま渡す - if (processed < validOutputs) { - // 参照モード: ViewPortから新しいImageBufferを作成 - RenderResponse &ref = - makeResponse(ImageBuffer(input.buffer().view()), input.origin); - downstream->pushProcess(ref, request); - } else { - // 最後: 元のバッファ参照をそのまま渡す - downstream->pushProcess(input, request); + + // 各出力に参照モードImageBufferを配信 + int processed = 0; + for (int i = 0; i < numOutputs; ++i) { + Node *downstream = downstreamNode(i); + if (!downstream) continue; + + ++processed; + + // 参照モードImageBufferを作成(メモリ解放しない) + // 最後の出力には元のバッファの参照をそのまま渡す + if (processed < validOutputs) { + // 参照モード: ViewPortから新しいImageBufferを作成 + RenderResponse &ref = makeResponse(ImageBuffer(input.buffer().view()), input.origin); + downstream->pushProcess(ref, request); + } else { + // 最後: 元のバッファ参照をそのまま渡す + downstream->pushProcess(input, request); + } } - } } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_DISTRIBUTOR_NODE_H +#endif // FLEXIMG_DISTRIBUTOR_NODE_H diff --git a/src/fleximg/nodes/filter_node_base.h b/src/fleximg/nodes/filter_node_base.h index d78ddad..912ef29 100644 --- a/src/fleximg/nodes/filter_node_base.h +++ b/src/fleximg/nodes/filter_node_base.h @@ -38,50 +38,56 @@ namespace FLEXIMG_NAMESPACE { class FilterNodeBase : public Node { public: - FilterNodeBase() { - initPorts(1, 1); // 入力1、出力1 - } + FilterNodeBase() + { + initPorts(1, 1); // 入力1、出力1 + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "FilterNodeBase"; } + const char *name() const override + { + return "FilterNodeBase"; + } - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPullProcess: マージン追加とメトリクス記録を行い、process() に委譲 - RenderResponse &onPullProcess(const RenderRequest &request) override; + // onPullProcess: マージン追加とメトリクス記録を行い、process() に委譲 + RenderResponse &onPullProcess(const RenderRequest &request) override; protected: - // ======================================== - // 派生クラスがオーバーライドするフック - // ======================================== + // ======================================== + // 派生クラスがオーバーライドするフック + // ======================================== - /// ラインフィルタ関数を返す(派生クラスで実装) - virtual filters::LineFilterFunc getFilterFunc() const = 0; + /// ラインフィルタ関数を返す(派生クラスで実装) + virtual filters::LineFilterFunc getFilterFunc() const = 0; - /// 入力マージン(ブラー等で拡大が必要な場合にオーバーライド) - virtual int computeInputMargin() const { return 0; } + /// 入力マージン(ブラー等で拡大が必要な場合にオーバーライド) + virtual int computeInputMargin() const + { + return 0; + } - /// メトリクス用ノードタイプ(派生クラスで実装) - int nodeTypeForMetrics() const override = 0; + /// メトリクス用ノードタイプ(派生クラスで実装) + int nodeTypeForMetrics() const override = 0; - // process() 共通実装 - // スキャンライン必須仕様(height=1)前提の共通処理 - RenderResponse &process(RenderResponse &input, - const RenderRequest &request) override; + // process() 共通実装 + // スキャンライン必須仕様(height=1)前提の共通処理 + RenderResponse &process(RenderResponse &input, const RenderRequest &request) override; - // ======================================== - // パラメータ(派生クラスからアクセス可能) - // ======================================== + // ======================================== + // パラメータ(派生クラスからアクセス可能) + // ======================================== - filters::LineFilterParams params_; + filters::LineFilterParams params_; }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -94,29 +100,26 @@ namespace FLEXIMG_NAMESPACE { // FilterNodeBase - Template Method フック実装 // ============================================================================ -RenderResponse &FilterNodeBase::onPullProcess(const RenderRequest &request) { - Node *upstream = upstreamNode(0); - if (!upstream) - return makeEmptyResponse(request.origin); +RenderResponse &FilterNodeBase::onPullProcess(const RenderRequest &request) +{ + Node *upstream = upstreamNode(0); + if (!upstream) return makeEmptyResponse(request.origin); - int margin = computeInputMargin(); - RenderRequest inputReq = request.expand(margin); + int margin = computeInputMargin(); + RenderRequest inputReq = request.expand(margin); #ifdef FLEXIMG_DEBUG_PERF_METRICS - // ピクセル効率計測 - auto &metrics = PerfMetrics::instance().nodes[nodeTypeForMetrics()]; - metrics.requestedPixels += static_cast(inputReq.width) * - static_cast(inputReq.height); - metrics.usedPixels += static_cast(request.width) * - static_cast(request.height); + // ピクセル効率計測 + auto &metrics = PerfMetrics::instance().nodes[nodeTypeForMetrics()]; + metrics.requestedPixels += static_cast(inputReq.width) * static_cast(inputReq.height); + metrics.usedPixels += static_cast(request.width) * static_cast(request.height); #endif - RenderResponse &input = upstream->pullProcess(inputReq); - if (!input.isValid()) - return input; + RenderResponse &input = upstream->pullProcess(inputReq); + if (!input.isValid()) return input; - // process() を呼ぶ(Node基底クラスの設計に沿う) - return process(input, request); + // process() を呼ぶ(Node基底クラスの設計に沿う) + return process(input, request); } // ============================================================================ @@ -129,29 +132,29 @@ RenderResponse &FilterNodeBase::onPullProcess(const RenderRequest &request) { // 3. パフォーマンス計測(デバッグビルド時) // -RenderResponse &FilterNodeBase::process(RenderResponse &input, - const RenderRequest &request) { - (void)request; // スキャンライン必須仕様では未使用 - FLEXIMG_METRICS_SCOPE(nodeTypeForMetrics()); +RenderResponse &FilterNodeBase::process(RenderResponse &input, const RenderRequest &request) +{ + (void)request; // スキャンライン必須仕様では未使用 + FLEXIMG_METRICS_SCOPE(nodeTypeForMetrics()); - // フォーマット変換を実行(メトリクス記録付き) - consolidateIfNeeded(input, PixelFormatIDs::RGBA8_Straight); + // フォーマット変換を実行(メトリクス記録付き) + consolidateIfNeeded(input, PixelFormatIDs::RGBA8_Straight); - // input.buffer() を直接加工 - ImageBuffer &working = input.buffer(); - ViewPort workingView = working.view(); + // input.buffer() を直接加工 + ImageBuffer &working = input.buffer(); + ViewPort workingView = working.view(); - // ラインフィルタを適用(height=1前提) - // ViewPortのx,yオフセットを考慮してpixelAt(0,0)を使用 - uint8_t *row = static_cast(workingView.pixelAt(0, 0)); - getFilterFunc()(row, workingView.width, params_); + // ラインフィルタを適用(height=1前提) + // ViewPortのx,yオフセットを考慮してpixelAt(0,0)を使用 + uint8_t *row = static_cast(workingView.pixelAt(0, 0)); + getFilterFunc()(row, workingView.width, params_); - // inputをそのまま返す(借用元への変更が反映される) - return input; + // inputをそのまま返す(借用元への変更が反映される) + return input; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_FILTER_NODE_BASE_H +#endif // FLEXIMG_FILTER_NODE_BASE_H diff --git a/src/fleximg/nodes/grayscale_node.h b/src/fleximg/nodes/grayscale_node.h index 64afc88..53924e6 100644 --- a/src/fleximg/nodes/grayscale_node.h +++ b/src/fleximg/nodes/grayscale_node.h @@ -19,19 +19,26 @@ namespace FLEXIMG_NAMESPACE { class GrayscaleNode : public FilterNodeBase { public: - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "GrayscaleNode"; } + const char *name() const override + { + return "GrayscaleNode"; + } protected: - filters::LineFilterFunc getFilterFunc() const override { - return &filters::grayscale_line; - } - int nodeTypeForMetrics() const override { return NodeType::Grayscale; } + filters::LineFilterFunc getFilterFunc() const override + { + return &filters::grayscale_line; + } + int nodeTypeForMetrics() const override + { + return NodeType::Grayscale; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_GRAYSCALE_NODE_H +#endif // FLEXIMG_GRAYSCALE_NODE_H diff --git a/src/fleximg/nodes/horizontal_blur_node.h b/src/fleximg/nodes/horizontal_blur_node.h index 056b00d..496acc0 100644 --- a/src/fleximg/nodes/horizontal_blur_node.h +++ b/src/fleximg/nodes/horizontal_blur_node.h @@ -4,8 +4,8 @@ #include "../core/node.h" #include "../core/perf_metrics.h" #include "../image/image_buffer.h" -#include // for std::min, std::max -#include // for std::memcpy +#include // for std::min, std::max +#include // for std::memcpy namespace FLEXIMG_NAMESPACE { @@ -39,131 +39,143 @@ namespace FLEXIMG_NAMESPACE { class HorizontalBlurNode : public Node { public: - HorizontalBlurNode() { initPorts(1, 1); } - - // ======================================== - // パラメータ設定 - // ======================================== - - // パラメータ上限 - static constexpr int kMaxRadius = 127; // 実用上十分、メモリ消費も許容範囲 - static constexpr int kMaxPasses = 3; // ガウシアン近似に十分 - - void setRadius(int_fast16_t radius) { - radius_ = static_cast((radius < 0) ? 0 - : (radius > kMaxRadius) ? kMaxRadius - : radius); - } - - void setPasses(int_fast16_t passes) { - passes_ = static_cast((passes < 1) ? 1 - : (passes > kMaxPasses) ? kMaxPasses - : passes); - } - - int16_t radius() const { return radius_; } - int16_t passes() const { return passes_; } - int_fast16_t kernelSize() const { return radius_ * 2 + 1; } + HorizontalBlurNode() + { + initPorts(1, 1); + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // パラメータ設定 + // ======================================== - const char *name() const override { return "HorizontalBlurNode"; } + // パラメータ上限 + static constexpr int kMaxRadius = 127; // 実用上十分、メモリ消費も許容範囲 + static constexpr int kMaxPasses = 3; // ガウシアン近似に十分 - // getDataRange: 上流データ範囲をブラー分拡張して返す - DataRange getDataRange(const RenderRequest &request) const override { - if (radius_ == 0 || passes_ == 0) { - // パススルー時は上流の範囲をそのまま返す - Node *upstream = upstreamNode(0); - if (upstream) { - return upstream->getDataRange(request); - } - return DataRange(); + void setRadius(int_fast16_t radius) + { + radius_ = static_cast((radius < 0) ? 0 : (radius > kMaxRadius) ? kMaxRadius : radius); } - // 上流への拡張リクエストを作成(左方向に拡大) - auto totalMargin = static_cast(radius_ * passes_); - RenderRequest inputReq; - inputReq.width = static_cast(request.width + totalMargin * 2); - inputReq.height = 1; - inputReq.origin.x = request.origin.x - to_fixed(totalMargin); - inputReq.origin.y = request.origin.y; - - Node *upstream = upstreamNode(0); - if (!upstream) { - return DataRange(); + void setPasses(int_fast16_t passes) + { + passes_ = static_cast((passes < 1) ? 1 : (passes > kMaxPasses) ? kMaxPasses : passes); } - // 上流のデータ範囲を取得 - DataRange upstreamRange = upstream->getDataRange(inputReq); - if (!upstreamRange.hasData()) { - return DataRange(); + int16_t radius() const + { + return radius_; + } + int16_t passes() const + { + return passes_; + } + int_fast16_t kernelSize() const + { + return radius_ * 2 + 1; } - // ブラー処理後の範囲を計算 - // upstreamRangeはinputReq座標系 → request座標系への変換: X - totalMargin - // さらにブラー処理による両側拡張: -totalMargin / +totalMargin - // 結果: startX - 2*totalMargin, endX - int16_t blurredStartX = - static_cast(upstreamRange.startX - totalMargin * 2); - int16_t blurredEndX = static_cast(upstreamRange.endX); + // ======================================== + // Node インターフェース + // ======================================== - // request範囲にクランプ - if (blurredStartX < 0) - blurredStartX = 0; - if (blurredEndX > request.width) - blurredEndX = request.width; - - if (blurredStartX >= blurredEndX) { - return DataRange(); + const char *name() const override + { + return "HorizontalBlurNode"; } - return DataRange{blurredStartX, blurredEndX}; - } + // getDataRange: 上流データ範囲をブラー分拡張して返す + DataRange getDataRange(const RenderRequest &request) const override + { + if (radius_ == 0 || passes_ == 0) { + // パススルー時は上流の範囲をそのまま返す + Node *upstream = upstreamNode(0); + if (upstream) { + return upstream->getDataRange(request); + } + return DataRange(); + } + + // 上流への拡張リクエストを作成(左方向に拡大) + auto totalMargin = static_cast(radius_ * passes_); + RenderRequest inputReq; + inputReq.width = static_cast(request.width + totalMargin * 2); + inputReq.height = 1; + inputReq.origin.x = request.origin.x - to_fixed(totalMargin); + inputReq.origin.y = request.origin.y; + + Node *upstream = upstreamNode(0); + if (!upstream) { + return DataRange(); + } + + // 上流のデータ範囲を取得 + DataRange upstreamRange = upstream->getDataRange(inputReq); + if (!upstreamRange.hasData()) { + return DataRange(); + } + + // ブラー処理後の範囲を計算 + // upstreamRangeはinputReq座標系 → request座標系への変換: X - totalMargin + // さらにブラー処理による両側拡張: -totalMargin / +totalMargin + // 結果: startX - 2*totalMargin, endX + int16_t blurredStartX = static_cast(upstreamRange.startX - totalMargin * 2); + int16_t blurredEndX = static_cast(upstreamRange.endX); + + // request範囲にクランプ + if (blurredStartX < 0) blurredStartX = 0; + if (blurredEndX > request.width) blurredEndX = request.width; + + if (blurredStartX >= blurredEndX) { + return DataRange(); + } + + return DataRange{blurredStartX, blurredEndX}; + } protected: - int nodeTypeForMetrics() const override { return NodeType::HorizontalBlur; } + int nodeTypeForMetrics() const override + { + return NodeType::HorizontalBlur; + } - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPullPrepare: AABBをX方向に拡張 - PrepareResponse onPullPrepare(const PrepareRequest &request) override; + // onPullPrepare: AABBをX方向に拡張 + PrepareResponse onPullPrepare(const PrepareRequest &request) override; - // onPullProcess: 水平ブラー処理 - RenderResponse &onPullProcess(const RenderRequest &request) override; + // onPullProcess: 水平ブラー処理 + RenderResponse &onPullProcess(const RenderRequest &request) override; - // onPushProcess: 水平ブラー処理(push型) - void onPushProcess(RenderResponse &input, - const RenderRequest &request) override; + // onPushProcess: 水平ブラー処理(push型) + void onPushProcess(RenderResponse &input, const RenderRequest &request) override; private: - int16_t radius_ = 5; - int16_t passes_ = 1; // 1-3の範囲、デフォルト1 - - // 水平方向ブラー処理(共通) - void applyHorizontalBlur(const ViewPort &srcView, int_fast16_t inputOffset, - ImageBuffer &output); - - // ブラー済みピクセルを書き込み - void writeBlurredPixel(uint8_t *row, int_fast16_t x, uint32_t sumR, - uint32_t sumG, uint32_t sumB, uint32_t sumA) { - auto off = static_cast(x * 4); - uint32_t ks = static_cast(kernelSize()); - if (sumA > 0) { - row[off] = static_cast(sumR / sumA); - row[off + 1] = static_cast(sumG / sumA); - row[off + 2] = static_cast(sumB / sumA); - row[off + 3] = static_cast(sumA / ks); - } else { - row[off] = row[off + 1] = row[off + 2] = row[off + 3] = 0; + int16_t radius_ = 5; + int16_t passes_ = 1; // 1-3の範囲、デフォルト1 + + // 水平方向ブラー処理(共通) + void applyHorizontalBlur(const ViewPort &srcView, int_fast16_t inputOffset, ImageBuffer &output); + + // ブラー済みピクセルを書き込み + void writeBlurredPixel(uint8_t *row, int_fast16_t x, uint32_t sumR, uint32_t sumG, uint32_t sumB, uint32_t sumA) + { + auto off = static_cast(x * 4); + uint32_t ks = static_cast(kernelSize()); + if (sumA > 0) { + row[off] = static_cast(sumR / sumA); + row[off + 1] = static_cast(sumG / sumA); + row[off + 2] = static_cast(sumB / sumA); + row[off + 3] = static_cast(sumA / ks); + } else { + row[off] = row[off + 1] = row[off + 2] = row[off + 3] = 0; + } } - } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -176,221 +188,204 @@ namespace FLEXIMG_NAMESPACE { // HorizontalBlurNode - Template Method フック実装 // ============================================================================ -PrepareResponse -HorizontalBlurNode::onPullPrepare(const PrepareRequest &request) { - // 上流へ伝播 - Node *upstream = upstreamNode(0); - if (!upstream) { - // 上流なし: サイズ0を返す - PrepareResponse result; - result.status = PrepareStatus::Prepared; - return result; - } - - PrepareResponse upstreamResult = upstream->pullPrepare(request); - if (!upstreamResult.ok()) { - return upstreamResult; - } +PrepareResponse HorizontalBlurNode::onPullPrepare(const PrepareRequest &request) +{ + // 上流へ伝播 + Node *upstream = upstreamNode(0); + if (!upstream) { + // 上流なし: サイズ0を返す + PrepareResponse result; + result.status = PrepareStatus::Prepared; + return result; + } - // radius=0の場合はパススルー - if (radius_ == 0) { - return upstreamResult; - } + PrepareResponse upstreamResult = upstream->pullPrepare(request); + if (!upstreamResult.ok()) { + return upstreamResult; + } - // 水平ぼかしはX方向に radius * passes 分拡張する - // AABBの幅を拡張し、originのXをシフト(左方向に拡大) - auto expansion = static_cast(radius_ * passes_); - upstreamResult.width = - static_cast(upstreamResult.width + expansion * 2); - upstreamResult.origin.x = upstreamResult.origin.x - to_fixed(expansion); + // radius=0の場合はパススルー + if (radius_ == 0) { + return upstreamResult; + } + + // 水平ぼかしはX方向に radius * passes 分拡張する + // AABBの幅を拡張し、originのXをシフト(左方向に拡大) + auto expansion = static_cast(radius_ * passes_); + upstreamResult.width = static_cast(upstreamResult.width + expansion * 2); + upstreamResult.origin.x = upstreamResult.origin.x - to_fixed(expansion); - return upstreamResult; + return upstreamResult; } -RenderResponse & -HorizontalBlurNode::onPullProcess(const RenderRequest &request) { - Node *upstream = upstreamNode(0); - if (!upstream) - return makeEmptyResponse(request.origin); - - // radius=0またはpasses=0の場合は処理をスキップしてスルー出力 - if (radius_ == 0 || passes_ == 0) { - return upstream->pullProcess(request); - } - - // マージンを計算して上流への要求を拡大 - auto totalMargin = - static_cast(radius_ * passes_); // 片側のマージン - RenderRequest inputReq; - inputReq.width = static_cast( - request.width + totalMargin * 2); // 両側にマージンを追加 - inputReq.height = 1; - inputReq.origin.x = - request.origin.x - to_fixed(totalMargin); // 左側にマージン分拡張 - inputReq.origin.y = request.origin.y; - - // 上流のデータ範囲を取得して出力バッファサイズを最適化 - DataRange upstreamRange = upstream->getDataRange(inputReq); - if (!upstreamRange.hasData()) { - return makeEmptyResponse(request.origin); - } - - RenderResponse &input = upstream->pullProcess(inputReq); - if (!input.isValid()) - return makeEmptyResponse(request.origin); - - // バッファ準備 - consolidateIfNeeded(input); - - FLEXIMG_METRICS_SCOPE(NodeType::HorizontalBlur); +RenderResponse &HorizontalBlurNode::onPullProcess(const RenderRequest &request) +{ + Node *upstream = upstreamNode(0); + if (!upstream) return makeEmptyResponse(request.origin); + + // radius=0またはpasses=0の場合は処理をスキップしてスルー出力 + if (radius_ == 0 || passes_ == 0) { + return upstream->pullProcess(request); + } + + // マージンを計算して上流への要求を拡大 + auto totalMargin = static_cast(radius_ * passes_); // 片側のマージン + RenderRequest inputReq; + inputReq.width = static_cast(request.width + totalMargin * 2); // 両側にマージンを追加 + inputReq.height = 1; + inputReq.origin.x = request.origin.x - to_fixed(totalMargin); // 左側にマージン分拡張 + inputReq.origin.y = request.origin.y; + + // 上流のデータ範囲を取得して出力バッファサイズを最適化 + DataRange upstreamRange = upstream->getDataRange(inputReq); + if (!upstreamRange.hasData()) { + return makeEmptyResponse(request.origin); + } + + RenderResponse &input = upstream->pullProcess(inputReq); + if (!input.isValid()) return makeEmptyResponse(request.origin); + + // バッファ準備 + consolidateIfNeeded(input); + + FLEXIMG_METRICS_SCOPE(NodeType::HorizontalBlur); #ifdef FLEXIMG_DEBUG_PERF_METRICS - auto &metrics = PerfMetrics::instance().nodes[NodeType::HorizontalBlur]; - metrics.requestedPixels += static_cast(request.width) * 1; - metrics.usedPixels += static_cast(inputReq.width) * 1; + auto &metrics = PerfMetrics::instance().nodes[NodeType::HorizontalBlur]; + metrics.requestedPixels += static_cast(request.width) * 1; + metrics.usedPixels += static_cast(inputReq.width) * 1; #endif - // RGBA8_Straightに変換 - ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), - PixelFormatIDs::RGBA8_Straight); + // RGBA8_Straightに変換 + ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), PixelFormatIDs::RGBA8_Straight); - // 上流から返されたoriginを保存 - Point currentOrigin = input.origin; + // 上流から返されたoriginを保存 + Point currentOrigin = input.origin; - // passes回、水平ブラーを適用(各パスで拡張+origin調整) - for (int_fast16_t pass = 0; pass < passes_; pass++) { - ViewPort srcView = buffer.view(); - auto inputWidth = static_cast(srcView.width); - auto outputWidth = static_cast(inputWidth + radius_ * 2); + // passes回、水平ブラーを適用(各パスで拡張+origin調整) + for (int_fast16_t pass = 0; pass < passes_; pass++) { + ViewPort srcView = buffer.view(); + auto inputWidth = static_cast(srcView.width); + auto outputWidth = static_cast(inputWidth + radius_ * 2); #ifdef FLEXIMG_DEBUG_PERF_METRICS - if (pass == 0) { - metrics.recordAlloc(static_cast(outputWidth) * 4, outputWidth, 1); - } + if (pass == 0) { + metrics.recordAlloc(static_cast(outputWidth) * 4, outputWidth, 1); + } #endif - // 出力バッファを確保 - ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Uninitialized); - - // 水平方向スライディングウィンドウでブラー処理 - // inputOffset = -radius (出力を左に拡張) - applyHorizontalBlur(srcView, -radius_, output); - - // origin.xを左に拡張した分だけ減らす(ワールド座標で左に移動) - currentOrigin.x = currentOrigin.x - to_fixed(radius_); - - buffer = std::move(output); - } - - // ブラー処理後の範囲を計算 - // upstreamRangeはinputReq座標系 → request座標系への変換: X - totalMargin - // さらにブラー処理による両側拡張: -totalMargin / +totalMargin - // 結果: startX - 2*totalMargin, endX - int16_t blurredStartX = - static_cast(upstreamRange.startX - totalMargin * 2); - int16_t blurredEndX = static_cast(upstreamRange.endX); - // request範囲にクランプ - if (blurredStartX < 0) - blurredStartX = 0; - if (blurredEndX > request.width) - blurredEndX = request.width; - - if (blurredStartX >= blurredEndX) { - return makeEmptyResponse(request.origin); - } - - int16_t outputWidth = blurredEndX - blurredStartX; - - // origin座標を基準にクロップ位置を計算 - int_fixed offsetX = currentOrigin.x - request.origin.x; - auto cropOffset = static_cast(from_fixed(offsetX)); - - // 出力バッファを確保(必要幅のみ、ゼロ初期化) - // 出力バッファ左端のワールド座標 = リクエスト左端 + blurredStartX - int_fixed outputOriginX = request.origin.x + to_fixed(blurredStartX); - ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Zero); - const uint8_t *srcRow = static_cast(buffer.view().data); - uint8_t *dstRow = static_cast(output.view().data); - - // クロップ範囲を計算(境界チェック付き) - // cropOffset = ブラー後バッファ左端 - リクエスト左端(ワールド座標差) - // blurredStartX = 出力範囲の開始位置(リクエスト座標系) - // ブラー後バッファ内での位置 = blurredStartX - cropOffset - auto srcStartX = std::max(0, blurredStartX - cropOffset); - auto dstStartX = std::max(0, cropOffset - blurredStartX); - auto copyWidth = std::min( - static_cast(buffer.width()) - srcStartX, - static_cast(outputWidth) - dstStartX); - - // 有効な範囲をコピー(範囲外は既にゼロ初期化済み) - if (copyWidth > 0) { - std::memcpy(dstRow + dstStartX * 4, srcRow + srcStartX * 4, - static_cast(copyWidth) * 4); - } - - return makeResponse(std::move(output), - Point{outputOriginX, request.origin.y}); + // 出力バッファを確保 + ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); + + // 水平方向スライディングウィンドウでブラー処理 + // inputOffset = -radius (出力を左に拡張) + applyHorizontalBlur(srcView, -radius_, output); + + // origin.xを左に拡張した分だけ減らす(ワールド座標で左に移動) + currentOrigin.x = currentOrigin.x - to_fixed(radius_); + + buffer = std::move(output); + } + + // ブラー処理後の範囲を計算 + // upstreamRangeはinputReq座標系 → request座標系への変換: X - totalMargin + // さらにブラー処理による両側拡張: -totalMargin / +totalMargin + // 結果: startX - 2*totalMargin, endX + int16_t blurredStartX = static_cast(upstreamRange.startX - totalMargin * 2); + int16_t blurredEndX = static_cast(upstreamRange.endX); + // request範囲にクランプ + if (blurredStartX < 0) blurredStartX = 0; + if (blurredEndX > request.width) blurredEndX = request.width; + + if (blurredStartX >= blurredEndX) { + return makeEmptyResponse(request.origin); + } + + int16_t outputWidth = blurredEndX - blurredStartX; + + // origin座標を基準にクロップ位置を計算 + int_fixed offsetX = currentOrigin.x - request.origin.x; + auto cropOffset = static_cast(from_fixed(offsetX)); + + // 出力バッファを確保(必要幅のみ、ゼロ初期化) + // 出力バッファ左端のワールド座標 = リクエスト左端 + blurredStartX + int_fixed outputOriginX = request.origin.x + to_fixed(blurredStartX); + ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero); + const uint8_t *srcRow = static_cast(buffer.view().data); + uint8_t *dstRow = static_cast(output.view().data); + + // クロップ範囲を計算(境界チェック付き) + // cropOffset = ブラー後バッファ左端 - リクエスト左端(ワールド座標差) + // blurredStartX = 出力範囲の開始位置(リクエスト座標系) + // ブラー後バッファ内での位置 = blurredStartX - cropOffset + auto srcStartX = std::max(0, blurredStartX - cropOffset); + auto dstStartX = std::max(0, cropOffset - blurredStartX); + auto copyWidth = std::min(static_cast(buffer.width()) - srcStartX, + static_cast(outputWidth) - dstStartX); + + // 有効な範囲をコピー(範囲外は既にゼロ初期化済み) + if (copyWidth > 0) { + std::memcpy(dstRow + dstStartX * 4, srcRow + srcStartX * 4, static_cast(copyWidth) * 4); + } + + return makeResponse(std::move(output), Point{outputOriginX, request.origin.y}); } -void HorizontalBlurNode::onPushProcess(RenderResponse &input, - const RenderRequest &request) { - // radius=0またはpasses=0の場合はスルー - if (radius_ == 0 || passes_ == 0) { - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushProcess(input, request); +void HorizontalBlurNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + // radius=0またはpasses=0の場合はスルー + if (radius_ == 0 || passes_ == 0) { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(input, request); + } + return; + } + + if (!input.isValid()) { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(input, request); + } + return; + } + + // バッファ準備 + consolidateIfNeeded(input); + + FLEXIMG_METRICS_SCOPE(NodeType::HorizontalBlur); + + // RGBA8_Straightに変換 + ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), PixelFormatIDs::RGBA8_Straight); + Point currentOrigin = input.origin; + + // passes回、水平ブラーを適用 + for (int_fast16_t pass = 0; pass < passes_; pass++) { + ViewPort srcView = buffer.view(); + auto inputWidth = static_cast(srcView.width); + auto outputWidth = static_cast(inputWidth + radius_ * 2); + + // 出力バッファを確保 + ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); + + // 水平方向スライディングウィンドウでブラー処理 + // push型では inputOffset = -radius + applyHorizontalBlur(srcView, -radius_, output); + + // origin.xを左に拡張した分だけ減らす(ワールド座標で左に移動) + currentOrigin.x = currentOrigin.x - to_fixed(radius_); + + buffer = std::move(output); } - return; - } - if (!input.isValid()) { + // 下流にpush Node *downstream = downstreamNode(0); if (downstream) { - downstream->pushProcess(input, request); + RenderRequest outReq = request; + outReq.width = static_cast(buffer.width()); + RenderResponse &resp = makeResponse(std::move(buffer), currentOrigin); + downstream->pushProcess(resp, outReq); } - return; - } - - // バッファ準備 - consolidateIfNeeded(input); - - FLEXIMG_METRICS_SCOPE(NodeType::HorizontalBlur); - - // RGBA8_Straightに変換 - ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), - PixelFormatIDs::RGBA8_Straight); - Point currentOrigin = input.origin; - - // passes回、水平ブラーを適用 - for (int_fast16_t pass = 0; pass < passes_; pass++) { - ViewPort srcView = buffer.view(); - auto inputWidth = static_cast(srcView.width); - auto outputWidth = static_cast(inputWidth + radius_ * 2); - - // 出力バッファを確保 - ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Uninitialized); - - // 水平方向スライディングウィンドウでブラー処理 - // push型では inputOffset = -radius - applyHorizontalBlur(srcView, -radius_, output); - - // origin.xを左に拡張した分だけ減らす(ワールド座標で左に移動) - currentOrigin.x = currentOrigin.x - to_fixed(radius_); - - buffer = std::move(output); - } - - // 下流にpush - Node *downstream = downstreamNode(0); - if (downstream) { - RenderRequest outReq = request; - outReq.width = static_cast(buffer.width()); - RenderResponse &resp = makeResponse(std::move(buffer), currentOrigin); - downstream->pushProcess(resp, outReq); - } } // ============================================================================ @@ -399,60 +394,59 @@ void HorizontalBlurNode::onPushProcess(RenderResponse &input, // 水平方向ブラー処理(共通) // inputOffset: 出力x=0に対応する入力のカーネル中心位置 -void HorizontalBlurNode::applyHorizontalBlur(const ViewPort &srcView, - int_fast16_t inputOffset, - ImageBuffer &output) { - const uint8_t *srcRow = static_cast(srcView.data); - uint8_t *dstRow = static_cast(output.view().data); - auto inputWidth = static_cast(srcView.width); - auto outputWidth = static_cast(output.width()); - - // 初期ウィンドウの合計(出力x=0に対応) - uint32_t sumR = 0, sumG = 0, sumB = 0, sumA = 0; - - for (auto kx = static_cast(-radius_); kx <= radius_; kx++) { - auto srcX = static_cast(inputOffset + kx); - if (srcX >= 0 && srcX < inputWidth) { - auto off = srcX * 4; - uint32_t a = srcRow[off + 3]; - sumR += srcRow[off] * a; - sumG += srcRow[off + 1] * a; - sumB += srcRow[off + 2] * a; - sumA += a; +void HorizontalBlurNode::applyHorizontalBlur(const ViewPort &srcView, int_fast16_t inputOffset, ImageBuffer &output) +{ + const uint8_t *srcRow = static_cast(srcView.data); + uint8_t *dstRow = static_cast(output.view().data); + auto inputWidth = static_cast(srcView.width); + auto outputWidth = static_cast(output.width()); + + // 初期ウィンドウの合計(出力x=0に対応) + uint32_t sumR = 0, sumG = 0, sumB = 0, sumA = 0; + + for (auto kx = static_cast(-radius_); kx <= radius_; kx++) { + auto srcX = static_cast(inputOffset + kx); + if (srcX >= 0 && srcX < inputWidth) { + auto off = srcX * 4; + uint32_t a = srcRow[off + 3]; + sumR += srcRow[off] * a; + sumG += srcRow[off + 1] * a; + sumB += srcRow[off + 2] * a; + sumA += a; + } } - } - writeBlurredPixel(dstRow, 0, sumR, sumG, sumB, sumA); - - // スライディング: x = 1 to outputWidth-1 - for (int_fast16_t x = 1; x < outputWidth; x++) { - // 出ていくピクセル - auto oldSrcX = static_cast(inputOffset + x - 1 - radius_); - if (oldSrcX >= 0 && oldSrcX < inputWidth) { - auto off = oldSrcX * 4; - uint32_t a = srcRow[off + 3]; - sumR -= srcRow[off] * a; - sumG -= srcRow[off + 1] * a; - sumB -= srcRow[off + 2] * a; - sumA -= a; + writeBlurredPixel(dstRow, 0, sumR, sumG, sumB, sumA); + + // スライディング: x = 1 to outputWidth-1 + for (int_fast16_t x = 1; x < outputWidth; x++) { + // 出ていくピクセル + auto oldSrcX = static_cast(inputOffset + x - 1 - radius_); + if (oldSrcX >= 0 && oldSrcX < inputWidth) { + auto off = oldSrcX * 4; + uint32_t a = srcRow[off + 3]; + sumR -= srcRow[off] * a; + sumG -= srcRow[off + 1] * a; + sumB -= srcRow[off + 2] * a; + sumA -= a; + } + + // 入ってくるピクセル + auto newSrcX = static_cast(inputOffset + x + radius_); + if (newSrcX >= 0 && newSrcX < inputWidth) { + auto off = newSrcX * 4; + uint32_t a = srcRow[off + 3]; + sumR += srcRow[off] * a; + sumG += srcRow[off + 1] * a; + sumB += srcRow[off + 2] * a; + sumA += a; + } + + writeBlurredPixel(dstRow, x, sumR, sumG, sumB, sumA); } - - // 入ってくるピクセル - auto newSrcX = static_cast(inputOffset + x + radius_); - if (newSrcX >= 0 && newSrcX < inputWidth) { - auto off = newSrcX * 4; - uint32_t a = srcRow[off + 3]; - sumR += srcRow[off] * a; - sumG += srcRow[off + 1] * a; - sumB += srcRow[off + 2] * a; - sumA += a; - } - - writeBlurredPixel(dstRow, x, sumR, sumG, sumB, sumA); - } } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_HORIZONTAL_BLUR_NODE_H +#endif // FLEXIMG_HORIZONTAL_BLUR_NODE_H diff --git a/src/fleximg/nodes/matte_node.h b/src/fleximg/nodes/matte_node.h index 0dded45..ff03d6e 100644 --- a/src/fleximg/nodes/matte_node.h +++ b/src/fleximg/nodes/matte_node.h @@ -43,125 +43,129 @@ namespace FLEXIMG_NAMESPACE { class MatteNode : public Node { public: - MatteNode() { - initPorts(3, 1); // 3入力、1出力 - } + MatteNode() + { + initPorts(3, 1); // 3入力、1出力 + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "MatteNode"; } + const char *name() const override + { + return "MatteNode"; + } - // getDataRange: 上流データ範囲の和集合を返す - DataRange getDataRange(const RenderRequest &request) const override; + // getDataRange: 上流データ範囲の和集合を返す + DataRange getDataRange(const RenderRequest &request) const override; #if defined(BENCH_M5STACK) || defined(BENCH_NATIVE) - // ======================================== - // ベンチマーク用公開API - // ======================================== + // ======================================== + // ベンチマーク用公開API + // ======================================== - // fgあり領域の行処理(ベンチマーク用ラッパー) - static void benchProcessRowWithFg(uint8_t *d, const uint8_t *m, - const uint8_t *s, int pixelCount); + // fgあり領域の行処理(ベンチマーク用ラッパー) + static void benchProcessRowWithFg(uint8_t *d, const uint8_t *m, const uint8_t *s, int pixelCount); - // fgなし領域の行処理(ベンチマーク用ラッパー) - static void benchProcessRowNoFg(uint8_t *d, const uint8_t *m, int pixelCount); + // fgなし領域の行処理(ベンチマーク用ラッパー) + static void benchProcessRowNoFg(uint8_t *d, const uint8_t *m, int pixelCount); #endif protected: - int nodeTypeForMetrics() const override { return NodeType::Matte; } + int nodeTypeForMetrics() const override + { + return NodeType::Matte; + } protected: - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPullPrepare: 全上流ノードにPrepareRequestを伝播 - PrepareResponse onPullPrepare(const PrepareRequest &request) override; + // onPullPrepare: 全上流ノードにPrepareRequestを伝播 + PrepareResponse onPullPrepare(const PrepareRequest &request) override; - // onPullFinalize: 全上流ノードに終了を伝播 - void onPullFinalize() override; + // onPullFinalize: 全上流ノードに終了を伝播 + void onPullFinalize() override; - // onPullProcess: マット合成処理 - RenderResponse &onPullProcess(const RenderRequest &request) override; + // onPullProcess: マット合成処理 + RenderResponse &onPullProcess(const RenderRequest &request) override; private: - // ======================================== - // ヘルパー構造体・関数 - // ======================================== - - // 入力画像のビュー情報(座標変換済み) - struct InputView { - const uint8_t *ptr = nullptr; - int16_t width = 0, height = 0; - int32_t stride = 0; - int16_t offsetX = 0, offsetY = 0; - - bool valid() const { return ptr != nullptr; } - - // 指定Y座標の行ポインタ(範囲外ならnullptr) - const uint8_t *rowAt(int_fast16_t y) const { - auto srcY = static_cast(y - offsetY); - if (static_cast(srcY) >= static_cast(height)) - return nullptr; - return ptr + srcY * stride; - } + // ======================================== + // ヘルパー構造体・関数 + // ======================================== + + // 入力画像のビュー情報(座標変換済み) + struct InputView { + const uint8_t *ptr = nullptr; + int16_t width = 0, height = 0; + int32_t stride = 0; + int16_t offsetX = 0, offsetY = 0; + + bool valid() const + { + return ptr != nullptr; + } - // RenderResponseから構築 - static InputView from(const RenderResponse &resp, int_fixed outOriginX, - int_fixed outOriginY) { - InputView v; - if (!resp.isValid()) - return v; - ViewPort vp = resp.view(); - v.ptr = static_cast(vp.data) + vp.y * vp.stride + - vp.x * vp.bytesPerPixel(); - v.width = vp.width; - v.height = vp.height; - v.stride = vp.stride; - v.offsetX = static_cast(from_fixed(resp.origin.x - outOriginX)); - v.offsetY = static_cast(from_fixed(resp.origin.y - outOriginY)); - return v; - } - }; - - // マスクの左右0スキップ範囲をスキャン(4バイト単位、アライメント対応) - // 戻り値: 有効範囲の幅(0なら全面0) - static int_fast16_t scanMaskZeroRanges(const uint8_t *maskData, - int_fast16_t maskWidth, - int_fast16_t &outLeftSkip, - int_fast16_t &outRightSkip); - - // ======================================== - // 合成処理 - // ======================================== - - // マット合成の実処理(出力には既にbgがコピー済み前提) - // alpha=0: 何もしない(出力に既にbgがある) - // alpha=255: fgをコピー - // 中間alpha: out = out*(1-a) + fg*a - void applyMatteOverlay(ImageBuffer &output, int_fast16_t outWidth, - const InputView &fg, const InputView &mask); - - // ======================================== - // キャッシュ(getDataRange→onPullProcess間で再利用) - // ======================================== - struct RangeCache { - Point origin{}; // キャッシュ時のリクエストorigin - DataRange fgRange{}; // fg のデータ範囲 - DataRange bgRange{}; // bg のデータ範囲 - DataRange maskRange{}; // mask のデータ範囲 - DataRange unionRange{}; // 全体の和集合 - bool valid = false; // キャッシュ有効フラグ - }; - mutable RangeCache rangeCache_; - - // 上流データ範囲を計算(キャッシュに保存) - DataRange calcUpstreamRanges(const RenderRequest &request) const; + // 指定Y座標の行ポインタ(範囲外ならnullptr) + const uint8_t *rowAt(int_fast16_t y) const + { + auto srcY = static_cast(y - offsetY); + if (static_cast(srcY) >= static_cast(height)) return nullptr; + return ptr + srcY * stride; + } + + // RenderResponseから構築 + static InputView from(const RenderResponse &resp, int_fixed outOriginX, int_fixed outOriginY) + { + InputView v; + if (!resp.isValid()) return v; + ViewPort vp = resp.view(); + v.ptr = static_cast(vp.data) + vp.y * vp.stride + vp.x * vp.bytesPerPixel(); + v.width = vp.width; + v.height = vp.height; + v.stride = vp.stride; + v.offsetX = static_cast(from_fixed(resp.origin.x - outOriginX)); + v.offsetY = static_cast(from_fixed(resp.origin.y - outOriginY)); + return v; + } + }; + + // マスクの左右0スキップ範囲をスキャン(4バイト単位、アライメント対応) + // 戻り値: 有効範囲の幅(0なら全面0) + static int_fast16_t scanMaskZeroRanges(const uint8_t *maskData, int_fast16_t maskWidth, int_fast16_t &outLeftSkip, + int_fast16_t &outRightSkip); + + // ======================================== + // 合成処理 + // ======================================== + + // マット合成の実処理(出力には既にbgがコピー済み前提) + // alpha=0: 何もしない(出力に既にbgがある) + // alpha=255: fgをコピー + // 中間alpha: out = out*(1-a) + fg*a + void applyMatteOverlay(ImageBuffer &output, int_fast16_t outWidth, const InputView &fg, const InputView &mask); + + // ======================================== + // キャッシュ(getDataRange→onPullProcess間で再利用) + // ======================================== + struct RangeCache { + Point origin{}; // キャッシュ時のリクエストorigin + DataRange fgRange{}; // fg のデータ範囲 + DataRange bgRange{}; // bg のデータ範囲 + DataRange maskRange{}; // mask のデータ範囲 + DataRange unionRange{}; // 全体の和集合 + bool valid = false; // キャッシュ有効フラグ + }; + mutable RangeCache rangeCache_; + + // 上流データ範囲を計算(キャッシュに保存) + DataRange calcUpstreamRanges(const RenderRequest &request) const; }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -174,141 +178,134 @@ namespace FLEXIMG_NAMESPACE { // MatteNode - Template Method フック実装 // ============================================================================ -PrepareResponse MatteNode::onPullPrepare(const PrepareRequest &request) { - PrepareResponse merged; - merged.status = PrepareStatus::Prepared; - bool hasValidUpstream = false; - - // AABB和集合計算用(ワールド座標) - float minX = 0, minY = 0, maxX = 0, maxY = 0; - - // 全上流へ伝播し、結果をマージ(AABB和集合) - for (int_fast16_t i = 0; i < 3; ++i) { - Node *upstream = upstreamNode(i); - if (upstream) { - PrepareResponse result = upstream->pullPrepare(request); - if (!result.ok()) { - return result; // エラーを伝播 - } - - // 新座標系: originはバッファ左上のワールド座標 - float left = fixed_to_float(result.origin.x); - float top = fixed_to_float(result.origin.y); - float right = left + static_cast(result.width); - float bottom = top + static_cast(result.height); - - if (!hasValidUpstream) { - // 最初の結果でベースを初期化 - minX = left; - minY = top; - maxX = right; - maxY = bottom; - hasValidUpstream = true; - } else { - // 和集合(各辺のmin/max) - if (left < minX) - minX = left; - if (top < minY) - minY = top; - if (right > maxX) - maxX = right; - if (bottom > maxY) - maxY = bottom; - } +PrepareResponse MatteNode::onPullPrepare(const PrepareRequest &request) +{ + PrepareResponse merged; + merged.status = PrepareStatus::Prepared; + bool hasValidUpstream = false; + + // AABB和集合計算用(ワールド座標) + float minX = 0, minY = 0, maxX = 0, maxY = 0; + + // 全上流へ伝播し、結果をマージ(AABB和集合) + for (int_fast16_t i = 0; i < 3; ++i) { + Node *upstream = upstreamNode(i); + if (upstream) { + PrepareResponse result = upstream->pullPrepare(request); + if (!result.ok()) { + return result; // エラーを伝播 + } + + // 新座標系: originはバッファ左上のワールド座標 + float left = fixed_to_float(result.origin.x); + float top = fixed_to_float(result.origin.y); + float right = left + static_cast(result.width); + float bottom = top + static_cast(result.height); + + if (!hasValidUpstream) { + // 最初の結果でベースを初期化 + minX = left; + minY = top; + maxX = right; + maxY = bottom; + hasValidUpstream = true; + } else { + // 和集合(各辺のmin/max) + if (left < minX) minX = left; + if (top < minY) minY = top; + if (right > maxX) maxX = right; + if (bottom > maxY) maxY = bottom; + } + } + } + + if (hasValidUpstream) { + // 和集合結果をPrepareResponseに設定 + merged.width = static_cast(std::ceil(maxX - minX)); + merged.height = static_cast(std::ceil(maxY - minY)); + // 新座標系: originはバッファ左上のワールド座標 + merged.origin.x = float_to_fixed(minX); + merged.origin.y = float_to_fixed(minY); + // MatteNodeは常にRGBA8_Straightで出力 + merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; + } else { + // 上流がない場合はサイズ0を返す + // width/height/originはデフォルト値(0)のまま } - } - - if (hasValidUpstream) { - // 和集合結果をPrepareResponseに設定 - merged.width = static_cast(std::ceil(maxX - minX)); - merged.height = static_cast(std::ceil(maxY - minY)); - // 新座標系: originはバッファ左上のワールド座標 - merged.origin.x = float_to_fixed(minX); - merged.origin.y = float_to_fixed(minY); - // MatteNodeは常にRGBA8_Straightで出力 - merged.preferredFormat = PixelFormatIDs::RGBA8_Straight; - } else { - // 上流がない場合はサイズ0を返す - // width/height/originはデフォルト値(0)のまま - } - - // 準備処理 - RenderRequest screenInfo; - screenInfo.width = request.width; - screenInfo.height = request.height; - screenInfo.origin = request.origin; - prepare(screenInfo); - - return merged; + + // 準備処理 + RenderRequest screenInfo; + screenInfo.width = request.width; + screenInfo.height = request.height; + screenInfo.origin = request.origin; + prepare(screenInfo); + + return merged; } -void MatteNode::onPullFinalize() { - finalize(); - for (int_fast16_t i = 0; i < 3; ++i) { - Node *upstream = upstreamNode(i); - if (upstream) { - upstream->pullFinalize(); +void MatteNode::onPullFinalize() +{ + finalize(); + for (int_fast16_t i = 0; i < 3; ++i) { + Node *upstream = upstreamNode(i); + if (upstream) { + upstream->pullFinalize(); + } } - } } // ============================================================================ // MatteNode - getDataRange実装 // ============================================================================ -DataRange MatteNode::calcUpstreamRanges(const RenderRequest &request) const { - Node *fgNode = upstreamNode(0); - Node *bgNode = upstreamNode(1); - Node *maskNode = upstreamNode(2); - - // 各上流のデータ範囲を取得 - rangeCache_.fgRange = fgNode ? fgNode->getDataRange(request) : DataRange{}; - rangeCache_.bgRange = bgNode ? bgNode->getDataRange(request) : DataRange{}; - rangeCache_.maskRange = - maskNode ? maskNode->getDataRange(request) : DataRange{}; - - // 有効範囲を計算: bg ∪ (mask ∩ fg) - // - bgは常に有効(マスク範囲外やalpha=0でbgが見える) - // - fgはマスク範囲との交差部分のみ有効 - // - マスクのみ(fg/bgなし)は透明なので除外 - int16_t startX = request.width; - int16_t endX = 0; - - // bg範囲は常に有効 - if (rangeCache_.bgRange.hasData()) { - startX = rangeCache_.bgRange.startX; - endX = rangeCache_.bgRange.endX; - } - - // (mask ∩ fg)範囲を追加 - if (rangeCache_.maskRange.hasData() && rangeCache_.fgRange.hasData()) { - int16_t intersectStart = - std::max(rangeCache_.maskRange.startX, rangeCache_.fgRange.startX); - int16_t intersectEnd = - std::min(rangeCache_.maskRange.endX, rangeCache_.fgRange.endX); - if (intersectStart < intersectEnd) { - if (intersectStart < startX) - startX = intersectStart; - if (intersectEnd > endX) - endX = intersectEnd; +DataRange MatteNode::calcUpstreamRanges(const RenderRequest &request) const +{ + Node *fgNode = upstreamNode(0); + Node *bgNode = upstreamNode(1); + Node *maskNode = upstreamNode(2); + + // 各上流のデータ範囲を取得 + rangeCache_.fgRange = fgNode ? fgNode->getDataRange(request) : DataRange{}; + rangeCache_.bgRange = bgNode ? bgNode->getDataRange(request) : DataRange{}; + rangeCache_.maskRange = maskNode ? maskNode->getDataRange(request) : DataRange{}; + + // 有効範囲を計算: bg ∪ (mask ∩ fg) + // - bgは常に有効(マスク範囲外やalpha=0でbgが見える) + // - fgはマスク範囲との交差部分のみ有効 + // - マスクのみ(fg/bgなし)は透明なので除外 + int16_t startX = request.width; + int16_t endX = 0; + + // bg範囲は常に有効 + if (rangeCache_.bgRange.hasData()) { + startX = rangeCache_.bgRange.startX; + endX = rangeCache_.bgRange.endX; } - } - rangeCache_.unionRange = - (startX < endX) ? DataRange{startX, endX} : DataRange{}; - rangeCache_.origin = request.origin; - rangeCache_.valid = true; + // (mask ∩ fg)範囲を追加 + if (rangeCache_.maskRange.hasData() && rangeCache_.fgRange.hasData()) { + int16_t intersectStart = std::max(rangeCache_.maskRange.startX, rangeCache_.fgRange.startX); + int16_t intersectEnd = std::min(rangeCache_.maskRange.endX, rangeCache_.fgRange.endX); + if (intersectStart < intersectEnd) { + if (intersectStart < startX) startX = intersectStart; + if (intersectEnd > endX) endX = intersectEnd; + } + } - return rangeCache_.unionRange; -} + rangeCache_.unionRange = (startX < endX) ? DataRange{startX, endX} : DataRange{}; + rangeCache_.origin = request.origin; + rangeCache_.valid = true; -DataRange MatteNode::getDataRange(const RenderRequest &request) const { - // キャッシュが有効でoriginが一致すれば再利用 - if (rangeCache_.valid && rangeCache_.origin.x == request.origin.x && - rangeCache_.origin.y == request.origin.y) { return rangeCache_.unionRange; - } - return calcUpstreamRanges(request); +} + +DataRange MatteNode::getDataRange(const RenderRequest &request) const +{ + // キャッシュが有効でoriginが一致すれば再利用 + if (rangeCache_.valid && rangeCache_.origin.x == request.origin.x && rangeCache_.origin.y == request.origin.y) { + return rangeCache_.unionRange; + } + return calcUpstreamRanges(request); } // ============================================================================ @@ -324,337 +321,304 @@ DataRange MatteNode::getDataRange(const RenderRequest &request) const { // 5. 合成 // -RenderResponse &MatteNode::onPullProcess(const RenderRequest &request) { - Node *fgNode = upstreamNode(0); // 前景 - Node *bgNode = upstreamNode(1); // 背景 - Node *maskNode = upstreamNode(2); // マスク - - // ======================================================================== - // Step 1: mask取得・全面0判定 - // ======================================================================== - - // キャッシュ確認・更新 - if (!rangeCache_.valid || rangeCache_.origin.x != request.origin.x || - rangeCache_.origin.y != request.origin.y) { - calcUpstreamRanges(request); - } - - { - // maskデータなし or maskNodeなし → bg fallback - if (!rangeCache_.maskRange.hasData()) - goto fallback_bg; - if (!maskNode) - goto fallback_bg; - - // mask要求範囲をfg∪bgの有効X範囲に制限 - // fg/bgが存在しない領域のマスクは取得しても無駄 - int16_t fgBgStart = request.width; - int16_t fgBgEnd = 0; - if (rangeCache_.fgRange.hasData()) { - if (rangeCache_.fgRange.startX < fgBgStart) - fgBgStart = rangeCache_.fgRange.startX; - if (rangeCache_.fgRange.endX > fgBgEnd) - fgBgEnd = rangeCache_.fgRange.endX; - } - if (rangeCache_.bgRange.hasData()) { - if (rangeCache_.bgRange.startX < fgBgStart) - fgBgStart = rangeCache_.bgRange.startX; - if (rangeCache_.bgRange.endX > fgBgEnd) - fgBgEnd = rangeCache_.bgRange.endX; - } +RenderResponse &MatteNode::onPullProcess(const RenderRequest &request) +{ + Node *fgNode = upstreamNode(0); // 前景 + Node *bgNode = upstreamNode(1); // 背景 + Node *maskNode = upstreamNode(2); // マスク + + // ======================================================================== + // Step 1: mask取得・全面0判定 + // ======================================================================== - // fg∪bgが空 → マスク値に関わらず出力は透明 - if (fgBgStart >= fgBgEnd) { - rangeCache_.valid = false; - return makeEmptyResponse(request.origin); + // キャッシュ確認・更新 + if (!rangeCache_.valid || rangeCache_.origin.x != request.origin.x || rangeCache_.origin.y != request.origin.y) { + calcUpstreamRanges(request); } - // fg∪bgとmaskの交差範囲でmask要求を絞る - RenderRequest maskRequest = request; { - int16_t clampStart = std::max(fgBgStart, rangeCache_.maskRange.startX); - int16_t clampEnd = std::min(fgBgEnd, rangeCache_.maskRange.endX); - if (clampStart < clampEnd) { - maskRequest.origin.x = request.origin.x + to_fixed(clampStart); - maskRequest.width = clampEnd - clampStart; - } - } + // maskデータなし or maskNodeなし → bg fallback + if (!rangeCache_.maskRange.hasData()) goto fallback_bg; + if (!maskNode) goto fallback_bg; + + // mask要求範囲をfg∪bgの有効X範囲に制限 + // fg/bgが存在しない領域のマスクは取得しても無駄 + int16_t fgBgStart = request.width; + int16_t fgBgEnd = 0; + if (rangeCache_.fgRange.hasData()) { + if (rangeCache_.fgRange.startX < fgBgStart) fgBgStart = rangeCache_.fgRange.startX; + if (rangeCache_.fgRange.endX > fgBgEnd) fgBgEnd = rangeCache_.fgRange.endX; + } + if (rangeCache_.bgRange.hasData()) { + if (rangeCache_.bgRange.startX < fgBgStart) fgBgStart = rangeCache_.bgRange.startX; + if (rangeCache_.bgRange.endX > fgBgEnd) fgBgEnd = rangeCache_.bgRange.endX; + } - RenderResponse &maskResult = maskNode->pullProcess(maskRequest); - if (!maskResult.isValid()) - goto fallback_bg; + // fg∪bgが空 → マスク値に関わらず出力は透明 + if (fgBgStart >= fgBgEnd) { + rangeCache_.valid = false; + return makeEmptyResponse(request.origin); + } - // バッファ準備 - consolidateIfNeeded(maskResult); + // fg∪bgとmaskの交差範囲でmask要求を絞る + RenderRequest maskRequest = request; + { + int16_t clampStart = std::max(fgBgStart, rangeCache_.maskRange.startX); + int16_t clampEnd = std::min(fgBgEnd, rangeCache_.maskRange.endX); + if (clampStart < clampEnd) { + maskRequest.origin.x = request.origin.x + to_fixed(clampStart); + maskRequest.width = clampEnd - clampStart; + } + } - // Alpha8に変換 - if (maskResult.buffer().formatID() != PixelFormatIDs::Alpha8) { - maskResult.convertFormat(PixelFormatIDs::Alpha8); - } + RenderResponse &maskResult = maskNode->pullProcess(maskRequest); + if (!maskResult.isValid()) goto fallback_bg; - // 全面0判定(行スキャン)+ 有効範囲へのcrop - ViewPort maskView = maskResult.view(); - const uint8_t *maskData = static_cast(maskView.data); - int_fast16_t maskLeftSkip = 0, maskRightSkip = 0; - auto maskEffectiveWidth = scanMaskZeroRanges(maskData, maskView.width, - maskLeftSkip, maskRightSkip); - - // 全面0 → bg fallback - if (maskEffectiveWidth == 0) - goto fallback_bg; - - // マスクを有効範囲にcrop(左右の0領域をスキップ) - if (maskLeftSkip > 0 || maskRightSkip > 0) { - maskResult.buffer().cropView( - static_cast(maskLeftSkip), 0, - static_cast(maskEffectiveWidth), - static_cast(maskView.height)); - maskResult.origin.x += to_fixed(maskLeftSkip); - maskView = maskResult.view(); // cropされたビューを再取得 - } + // バッファ準備 + consolidateIfNeeded(maskResult); - // ======================================================================== - // Step 2: bg取得・出力領域計算 - // ======================================================================== + // Alpha8に変換 + if (maskResult.buffer().formatID() != PixelFormatIDs::Alpha8) { + maskResult.convertFormat(PixelFormatIDs::Alpha8); + } - RenderResponse *bgResultPtr = nullptr; - if (rangeCache_.bgRange.hasData() && bgNode) { - RenderResponse &bgResult = bgNode->pullProcess(request); - if (bgResult.isValid()) { - // バッファ準備 - consolidateIfNeeded(bgResult); - bgResultPtr = &bgResult; - } - } + // 全面0判定(行スキャン)+ 有効範囲へのcrop + ViewPort maskView = maskResult.view(); + const uint8_t *maskData = static_cast(maskView.data); + int_fast16_t maskLeftSkip = 0, maskRightSkip = 0; + auto maskEffectiveWidth = scanMaskZeroRanges(maskData, maskView.width, maskLeftSkip, maskRightSkip); + + // 全面0 → bg fallback + if (maskEffectiveWidth == 0) goto fallback_bg; + + // マスクを有効範囲にcrop(左右の0領域をスキップ) + if (maskLeftSkip > 0 || maskRightSkip > 0) { + maskResult.buffer().cropView(static_cast(maskLeftSkip), 0, + static_cast(maskEffectiveWidth), + static_cast(maskView.height)); + maskResult.origin.x += to_fixed(maskLeftSkip); + maskView = maskResult.view(); // cropされたビューを再取得 + } - // 出力領域計算(cropされたmask ∪ bg) - int_fixed unionMinX = maskResult.origin.x; - int_fixed unionMinY = maskResult.origin.y; - int_fixed unionMaxX = unionMinX + to_fixed(maskView.width); - int_fixed unionMaxY = unionMinY + to_fixed(maskView.height); - - if (bgResultPtr) { - ViewPort bgViewPort = bgResultPtr->view(); - int_fixed bgMinX = bgResultPtr->origin.x; - int_fixed bgMinY = bgResultPtr->origin.y; - int_fixed bgMaxX = bgMinX + to_fixed(bgViewPort.width); - int_fixed bgMaxY = bgMinY + to_fixed(bgViewPort.height); - if (bgMinX < unionMinX) - unionMinX = bgMinX; - if (bgMinY < unionMinY) - unionMinY = bgMinY; - if (bgMaxX > unionMaxX) - unionMaxX = bgMaxX; - if (bgMaxY > unionMaxY) - unionMaxY = bgMaxY; - } + // ======================================================================== + // Step 2: bg取得・出力領域計算 + // ======================================================================== + + RenderResponse *bgResultPtr = nullptr; + if (rangeCache_.bgRange.hasData() && bgNode) { + RenderResponse &bgResult = bgNode->pullProcess(request); + if (bgResult.isValid()) { + // バッファ準備 + consolidateIfNeeded(bgResult); + bgResultPtr = &bgResult; + } + } - auto unionWidth = - static_cast(from_fixed(unionMaxX - unionMinX)); - auto unionHeight = - static_cast(from_fixed(unionMaxY - unionMinY)); + // 出力領域計算(cropされたmask ∪ bg) + int_fixed unionMinX = maskResult.origin.x; + int_fixed unionMinY = maskResult.origin.y; + int_fixed unionMaxX = unionMinX + to_fixed(maskView.width); + int_fixed unionMaxY = unionMinY + to_fixed(maskView.height); + + if (bgResultPtr) { + ViewPort bgViewPort = bgResultPtr->view(); + int_fixed bgMinX = bgResultPtr->origin.x; + int_fixed bgMinY = bgResultPtr->origin.y; + int_fixed bgMaxX = bgMinX + to_fixed(bgViewPort.width); + int_fixed bgMaxY = bgMinY + to_fixed(bgViewPort.height); + if (bgMinX < unionMinX) unionMinX = bgMinX; + if (bgMinY < unionMinY) unionMinY = bgMinY; + if (bgMaxX > unionMaxX) unionMaxX = bgMaxX; + if (bgMaxY > unionMaxY) unionMaxY = bgMaxY; + } - // ======================================================================== - // Step 3: 出力バッファ作成(ゼロクリア)+ bgコピー - // ======================================================================== + auto unionWidth = static_cast(from_fixed(unionMaxX - unionMinX)); + auto unionHeight = static_cast(from_fixed(unionMaxY - unionMinY)); + + // ======================================================================== + // Step 3: 出力バッファ作成(ゼロクリア)+ bgコピー + // ======================================================================== - FLEXIMG_METRICS_SCOPE(NodeType::Matte); + FLEXIMG_METRICS_SCOPE(NodeType::Matte); - ImageBuffer outputBuf(unionWidth, unionHeight, - PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero, - allocator()); + ImageBuffer outputBuf(unionWidth, unionHeight, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero, allocator()); #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().nodes[NodeType::Matte].recordAlloc( - outputBuf.totalBytes(), outputBuf.width(), outputBuf.height()); + PerfMetrics::instance().nodes[NodeType::Matte].recordAlloc(outputBuf.totalBytes(), outputBuf.width(), + outputBuf.height()); #endif - // bgがあればコピー - if (bgResultPtr) { - auto bgOffsetX = static_cast( - from_fixed(bgResultPtr->origin.x - unionMinX)); - auto bgOffsetY = static_cast( - from_fixed(bgResultPtr->origin.y - unionMinY)); - - auto converter = resolveConverter(bgResultPtr->buffer().formatID(), - PixelFormatIDs::RGBA8_Straight, - &bgResultPtr->buffer().auxInfo()); - if (converter) { - ViewPort bgViewPort = bgResultPtr->view(); - ViewPort outView = outputBuf.view(); - auto srcBytesPerPixel = - static_cast(bgViewPort.bytesPerPixel()); - - // bgの有効範囲を計算(出力座標系) - auto copyStartX = std::max(0, bgOffsetX); - auto copyEndX = - std::min(unionWidth, bgOffsetX + bgViewPort.width); - auto copyStartY = std::max(0, bgOffsetY); - auto copyEndY = - std::min(unionHeight, bgOffsetY + bgViewPort.height); - auto copyWidth = static_cast(copyEndX - copyStartX); - - if (copyWidth > 0) { - auto srcStartX = static_cast(copyStartX - bgOffsetX); - for (auto y = copyStartY; y < copyEndY; ++y) { - auto srcY = static_cast(y - bgOffsetY); - const uint8_t *srcRow = - static_cast(bgViewPort.data) + - (bgViewPort.y + srcY) * bgViewPort.stride + - (bgViewPort.x + srcStartX) * srcBytesPerPixel; - uint8_t *dstRow = static_cast(outView.data) + - y * outView.stride + copyStartX * 4; - converter(dstRow, srcRow, copyWidth); - } + // bgがあればコピー + if (bgResultPtr) { + auto bgOffsetX = static_cast(from_fixed(bgResultPtr->origin.x - unionMinX)); + auto bgOffsetY = static_cast(from_fixed(bgResultPtr->origin.y - unionMinY)); + + auto converter = resolveConverter(bgResultPtr->buffer().formatID(), PixelFormatIDs::RGBA8_Straight, + &bgResultPtr->buffer().auxInfo()); + if (converter) { + ViewPort bgViewPort = bgResultPtr->view(); + ViewPort outView = outputBuf.view(); + auto srcBytesPerPixel = static_cast(bgViewPort.bytesPerPixel()); + + // bgの有効範囲を計算(出力座標系) + auto copyStartX = std::max(0, bgOffsetX); + auto copyEndX = std::min(unionWidth, bgOffsetX + bgViewPort.width); + auto copyStartY = std::max(0, bgOffsetY); + auto copyEndY = std::min(unionHeight, bgOffsetY + bgViewPort.height); + auto copyWidth = static_cast(copyEndX - copyStartX); + + if (copyWidth > 0) { + auto srcStartX = static_cast(copyStartX - bgOffsetX); + for (auto y = copyStartY; y < copyEndY; ++y) { + auto srcY = static_cast(y - bgOffsetY); + const uint8_t *srcRow = static_cast(bgViewPort.data) + + (bgViewPort.y + srcY) * bgViewPort.stride + + (bgViewPort.x + srcStartX) * srcBytesPerPixel; + uint8_t *dstRow = static_cast(outView.data) + y * outView.stride + copyStartX * 4; + converter(dstRow, srcRow, copyWidth); + } + } + } } - } - } - - // ======================================================================== - // Step 4: fg取得 - // ======================================================================== - RenderResponse *fgResultPtr = nullptr; - if (fgNode && rangeCache_.fgRange.hasData()) { - RenderResponse &fgResult = fgNode->pullProcess(request); - if (fgResult.isValid()) { - // バッファ準備 - consolidateIfNeeded(fgResult); - // RGBA8_Straightに変換 - if (fgResult.buffer().formatID() != PixelFormatIDs::RGBA8_Straight) { - fgResult.convertFormat(PixelFormatIDs::RGBA8_Straight); + // ======================================================================== + // Step 4: fg取得 + // ======================================================================== + + RenderResponse *fgResultPtr = nullptr; + if (fgNode && rangeCache_.fgRange.hasData()) { + RenderResponse &fgResult = fgNode->pullProcess(request); + if (fgResult.isValid()) { + // バッファ準備 + consolidateIfNeeded(fgResult); + // RGBA8_Straightに変換 + if (fgResult.buffer().formatID() != PixelFormatIDs::RGBA8_Straight) { + fgResult.convertFormat(PixelFormatIDs::RGBA8_Straight); + } + fgResultPtr = &fgResult; + } } - fgResultPtr = &fgResult; - } - } - // ======================================================================== - // Step 5: 合成 - // ======================================================================== + // ======================================================================== + // Step 5: 合成 + // ======================================================================== - // InputView::fromにはconst参照が必要なので、一時的なRenderResponseを使う - static RenderResponse emptyResult; - emptyResult.origin = Point{}; + // InputView::fromにはconst参照が必要なので、一時的なRenderResponseを使う + static RenderResponse emptyResult; + emptyResult.origin = Point{}; - InputView fgView = InputView::from(fgResultPtr ? *fgResultPtr : emptyResult, - unionMinX, unionMinY); - InputView maskInputView = InputView::from(maskResult, unionMinX, unionMinY); + InputView fgView = InputView::from(fgResultPtr ? *fgResultPtr : emptyResult, unionMinX, unionMinY); + InputView maskInputView = InputView::from(maskResult, unionMinX, unionMinY); - applyMatteOverlay(outputBuf, unionWidth, fgView, maskInputView); + applyMatteOverlay(outputBuf, unionWidth, fgView, maskInputView); - // キャッシュ無効化 - rangeCache_.valid = false; + // キャッシュ無効化 + rangeCache_.valid = false; - return makeResponse(std::move(outputBuf), Point{unionMinX, unionMinY}); - } + return makeResponse(std::move(outputBuf), Point{unionMinX, unionMinY}); + } - // bgフォールバック: mask無効時はbgを直接返却 + // bgフォールバック: mask無効時はbgを直接返却 fallback_bg: - rangeCache_.valid = false; - if (bgNode) { - return bgNode->pullProcess(request); - } - return makeEmptyResponse(request.origin); + rangeCache_.valid = false; + if (bgNode) { + return bgNode->pullProcess(request); + } + return makeEmptyResponse(request.origin); } // ============================================================================ // MatteNode - ヘルパー関数実装 // ============================================================================ -int_fast16_t MatteNode::scanMaskZeroRanges(const uint8_t *maskData, - int_fast16_t maskWidth, - int_fast16_t &outLeftSkip, - int_fast16_t &outRightSkip) { - // 左端からの0スキップ(4バイト単位、アライメント対応) - int_fast16_t leftSkip = 0; - { - // Phase 1: アライメントまで1バイトずつ - uintptr_t addr = reinterpret_cast(maskData); - int_fast16_t misalign = static_cast(addr & 3); - if (misalign != 0) { - int_fast16_t alignBytes = static_cast(4 - misalign); - if (alignBytes > maskWidth) { - alignBytes = maskWidth; - } - while (leftSkip < alignBytes && maskData[leftSkip] == 0) { - ++leftSkip; - } - alignBytes -= leftSkip; - if (leftSkip < maskWidth && maskData[leftSkip] != 0) { - outLeftSkip = leftSkip; - outRightSkip = 0; - // 右スキップも計算して返す - goto scan_right; - } - } - - // Phase 2: 4バイト単位(ポインタベース) +int_fast16_t MatteNode::scanMaskZeroRanges(const uint8_t *maskData, int_fast16_t maskWidth, int_fast16_t &outLeftSkip, + int_fast16_t &outRightSkip) +{ + // 左端からの0スキップ(4バイト単位、アライメント対応) + int_fast16_t leftSkip = 0; { - const uint32_t *p32 = - reinterpret_cast(maskData + leftSkip); - const uint32_t *p32_end = p32 + ((maskWidth - leftSkip) >> 2); - while (p32 < p32_end && *p32 == 0) { - ++p32; - } - leftSkip = static_cast( - reinterpret_cast(p32) - maskData); - } + // Phase 1: アライメントまで1バイトずつ + uintptr_t addr = reinterpret_cast(maskData); + int_fast16_t misalign = static_cast(addr & 3); + if (misalign != 0) { + int_fast16_t alignBytes = static_cast(4 - misalign); + if (alignBytes > maskWidth) { + alignBytes = maskWidth; + } + while (leftSkip < alignBytes && maskData[leftSkip] == 0) { + ++leftSkip; + } + alignBytes -= leftSkip; + if (leftSkip < maskWidth && maskData[leftSkip] != 0) { + outLeftSkip = leftSkip; + outRightSkip = 0; + // 右スキップも計算して返す + goto scan_right; + } + } + + // Phase 2: 4バイト単位(ポインタベース) + { + const uint32_t *p32 = reinterpret_cast(maskData + leftSkip); + const uint32_t *p32_end = p32 + ((maskWidth - leftSkip) >> 2); + while (p32 < p32_end && *p32 == 0) { + ++p32; + } + leftSkip = static_cast(reinterpret_cast(p32) - maskData); + } - // Phase 3: 残りを1バイトずつ - while (leftSkip < maskWidth && maskData[leftSkip] == 0) { - ++leftSkip; + // Phase 3: 残りを1バイトずつ + while (leftSkip < maskWidth && maskData[leftSkip] == 0) { + ++leftSkip; + } } - } - // 全面0なら終了 - if (leftSkip >= maskWidth) { - outLeftSkip = maskWidth; - outRightSkip = 0; - return 0; - } + // 全面0なら終了 + if (leftSkip >= maskWidth) { + outLeftSkip = maskWidth; + outRightSkip = 0; + return 0; + } scan_right: - outLeftSkip = leftSkip; - - // 右端からの0スキップ(4バイト単位、アライメント対応) - int_fast16_t rightSkip = 0; - { - const int_fast16_t limit = static_cast(maskWidth - leftSkip); - - // Phase 1: アライメントまで1バイトずつ - uintptr_t endAddr = reinterpret_cast(maskData + maskWidth); - int_fast16_t misalign = static_cast(endAddr & 3); - if (misalign > limit) { - misalign = limit; - } - while (rightSkip < misalign && maskData[maskWidth - 1 - rightSkip] == 0) { - ++rightSkip; - } - if (rightSkip < limit && maskData[maskWidth - 1 - rightSkip] != 0) { - outRightSkip = rightSkip; - return maskWidth - leftSkip - rightSkip; - } + outLeftSkip = leftSkip; - // Phase 2: 4バイト単位(ポインタベース) + // 右端からの0スキップ(4バイト単位、アライメント対応) + int_fast16_t rightSkip = 0; { - const uint32_t *p32 = - reinterpret_cast(maskData + maskWidth - rightSkip) - - 1; - const uint32_t *p32_end = p32 - ((limit - rightSkip) >> 2); - while (p32 > p32_end && *p32 == 0) { - --p32; - } - rightSkip = static_cast( - maskData + maskWidth - reinterpret_cast(p32 + 1)); - } + const int_fast16_t limit = static_cast(maskWidth - leftSkip); + + // Phase 1: アライメントまで1バイトずつ + uintptr_t endAddr = reinterpret_cast(maskData + maskWidth); + int_fast16_t misalign = static_cast(endAddr & 3); + if (misalign > limit) { + misalign = limit; + } + while (rightSkip < misalign && maskData[maskWidth - 1 - rightSkip] == 0) { + ++rightSkip; + } + if (rightSkip < limit && maskData[maskWidth - 1 - rightSkip] != 0) { + outRightSkip = rightSkip; + return maskWidth - leftSkip - rightSkip; + } + + // Phase 2: 4バイト単位(ポインタベース) + { + const uint32_t *p32 = reinterpret_cast(maskData + maskWidth - rightSkip) - 1; + const uint32_t *p32_end = p32 - ((limit - rightSkip) >> 2); + while (p32 > p32_end && *p32 == 0) { + --p32; + } + rightSkip = static_cast(maskData + maskWidth - reinterpret_cast(p32 + 1)); + } - // Phase 3: 残りを1バイトずつ - while (rightSkip < limit && maskData[maskWidth - 1 - rightSkip] == 0) { - ++rightSkip; + // Phase 3: 残りを1バイトずつ + while (rightSkip < limit && maskData[maskWidth - 1 - rightSkip] == 0) { + ++rightSkip; + } } - } - outRightSkip = rightSkip; - return maskWidth - leftSkip - rightSkip; + outRightSkip = rightSkip; + return maskWidth - leftSkip - rightSkip; } // ============================================================================ @@ -667,119 +631,101 @@ int_fast16_t MatteNode::scanMaskZeroRanges(const uint8_t *maskData, // - alpha=255: 透明(0,0,0,0)に書き込み(fgがないため) // - 中間alpha: bgをフェード(out = out * (1-alpha)) // ---------------------------------------------------------------------------- -static inline void processRowNoFg(uint8_t *__restrict__ d, - const uint8_t *__restrict__ m, - int_fast16_t pixelCount) { - if (pixelCount <= 0) - return; +static inline void processRowNoFg(uint8_t *__restrict__ d, const uint8_t *__restrict__ m, int_fast16_t pixelCount) +{ + if (pixelCount <= 0) return; - uint_fast8_t alpha = *m; + uint_fast8_t alpha = *m; - if (alpha == 0) - goto handle_alpha_0; - if (alpha == 255) - goto handle_alpha_255; + if (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; blend: - // ブレンドループ: do-whileで末尾デクリメント - // breakした時点でpixelCountはまだ減っていない - --m; - d -= 4; - do { + // ブレンドループ: do-whileで末尾デクリメント + // breakした時点でpixelCountはまだ減っていない + --m; + d -= 4; + do { + ++m; + alpha = *m; + d += 4; + if (alpha == 0) break; + if (alpha == 255) break; + uint32_t d32 = *reinterpret_cast(d); + // bgフェードのみ(fgなし): out = bg * (1-alpha) + // 256スケール正規化: inv_a_256 = 256 - (alpha + (alpha >> 7)) + // 精度: 91.6%が完全一致、最大誤差±1 + uint_fast16_t inv_a_256 = 256 - alpha - (alpha >> 7); + uint32_t d32_even = d32 & 0x00FF00FF; + uint32_t d32_odd = (d32 >> 8) & 0x00FF00FF; + d32_even *= inv_a_256; + d32_odd *= inv_a_256; + *reinterpret_cast(d) = d32_odd; + d[0] = static_cast(d32_even >> 8); + d[2] = static_cast(d32_even >> 24); + } while (--pixelCount > 0); + if (pixelCount <= 0) return; + if (alpha == 0) goto handle_alpha_0; + +handle_alpha_255: + // alpha=255でfgなし → 透明(0,0,0,0)に書き込み + *reinterpret_cast(d) = 0; + if (--pixelCount <= 0) return; ++m; alpha = *m; d += 4; - if (alpha == 0) - break; - if (alpha == 255) - break; - uint32_t d32 = *reinterpret_cast(d); - // bgフェードのみ(fgなし): out = bg * (1-alpha) - // 256スケール正規化: inv_a_256 = 256 - (alpha + (alpha >> 7)) - // 精度: 91.6%が完全一致、最大誤差±1 - uint_fast16_t inv_a_256 = 256 - alpha - (alpha >> 7); - uint32_t d32_even = d32 & 0x00FF00FF; - uint32_t d32_odd = (d32 >> 8) & 0x00FF00FF; - d32_even *= inv_a_256; - d32_odd *= inv_a_256; - *reinterpret_cast(d) = d32_odd; - d[0] = static_cast(d32_even >> 8); - d[2] = static_cast(d32_even >> 24); - } while (--pixelCount > 0); - if (pixelCount <= 0) - return; - if (alpha == 0) - goto handle_alpha_0; - -handle_alpha_255: - // alpha=255でfgなし → 透明(0,0,0,0)に書き込み - *reinterpret_cast(d) = 0; - if (--pixelCount <= 0) - return; - ++m; - alpha = *m; - d += 4; - // 4px単位で透明書き込み - { - auto plimit = pixelCount >> 2; - if (plimit && alpha == 255 && (reinterpret_cast(m) & 3) == 0) { - uint32_t m32 = reinterpret_cast(m)[0]; - auto m_start = m; - do { - if (m32 != 0xFFFFFFFFu) - break; - m32 = reinterpret_cast(m)[1]; - m += 4; - } while (--plimit); - if (m != m_start) { - auto len = static_cast(m - m_start); - std::memset(d, 0, static_cast(len) * 4); - pixelCount -= len; - if (pixelCount <= 0) - return; - alpha = static_cast(m32); - d += len * 4; - } + // 4px単位で透明書き込み + { + auto plimit = pixelCount >> 2; + if (plimit && alpha == 255 && (reinterpret_cast(m) & 3) == 0) { + uint32_t m32 = reinterpret_cast(m)[0]; + auto m_start = m; + do { + if (m32 != 0xFFFFFFFFu) break; + m32 = reinterpret_cast(m)[1]; + m += 4; + } while (--plimit); + if (m != m_start) { + auto len = static_cast(m - m_start); + std::memset(d, 0, static_cast(len) * 4); + pixelCount -= len; + if (pixelCount <= 0) return; + alpha = static_cast(m32); + d += len * 4; + } + } } - } - if (alpha == 255) - goto handle_alpha_255; - if (alpha != 0) - goto blend; + if (alpha == 255) goto handle_alpha_255; + if (alpha != 0) goto blend; handle_alpha_0: - if (--pixelCount <= 0) - return; - ++m; - alpha = *m; - d += 4; - // 4px単位スキップ - { - auto plimit = pixelCount >> 2; - if (plimit && alpha == 0 && (reinterpret_cast(m) & 3) == 0) { - uint32_t m32 = reinterpret_cast(m)[0]; - auto m_start = m; - do { - if (m32 != 0) - break; - m32 = reinterpret_cast(m)[1]; - m += 4; - } while (--plimit); - if (m != m_start) { - auto skipped = static_cast(m - m_start); - pixelCount -= skipped; - if (pixelCount <= 0) - return; - alpha = static_cast(m32); - d += skipped * 4; - } + if (--pixelCount <= 0) return; + ++m; + alpha = *m; + d += 4; + // 4px単位スキップ + { + auto plimit = pixelCount >> 2; + if (plimit && alpha == 0 && (reinterpret_cast(m) & 3) == 0) { + uint32_t m32 = reinterpret_cast(m)[0]; + auto m_start = m; + do { + if (m32 != 0) break; + m32 = reinterpret_cast(m)[1]; + m += 4; + } while (--plimit); + if (m != m_start) { + auto skipped = static_cast(m - m_start); + pixelCount -= skipped; + if (pixelCount <= 0) return; + alpha = static_cast(m32); + d += skipped * 4; + } + } } - } - if (alpha == 0) - goto handle_alpha_0; - if (alpha == 255) - goto handle_alpha_255; - goto blend; + if (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; + goto blend; } // ---------------------------------------------------------------------------- @@ -788,200 +734,171 @@ static inline void processRowNoFg(uint8_t *__restrict__ d, // - alpha=255: fgをコピー // - 中間alpha: フルブレンド(out = out*(1-alpha) + fg*alpha) // ---------------------------------------------------------------------------- -static inline void processRowWithFg(uint8_t *__restrict__ d, - const uint8_t *__restrict__ m, - const uint8_t *__restrict__ s, - int_fast16_t pixelCount) { - if (pixelCount <= 0) - return; +static inline void processRowWithFg(uint8_t *__restrict__ d, const uint8_t *__restrict__ m, + const uint8_t *__restrict__ s, int_fast16_t pixelCount) +{ + if (pixelCount <= 0) return; - uint_fast8_t alpha = *m; + uint_fast8_t alpha = *m; - if (alpha == 0) - goto handle_alpha_0; - if (alpha == 255) - goto handle_alpha_255; + if (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; blend: - // ブレンドループ: do-whileで末尾デクリメント - // breakした時点でpixelCountはまだ減っていない - --m; - d -= 4; - s -= 4; - do { + // ブレンドループ: do-whileで末尾デクリメント + // breakした時点でpixelCountはまだ減っていない + --m; + d -= 4; + s -= 4; + do { + ++m; + alpha = *m; + d += 4; + s += 4; + if (alpha == 0) break; + if (alpha == 255) break; + uint32_t d32 = *reinterpret_cast(d); + uint32_t s32 = *reinterpret_cast(s); + // fg/bg両方のブレンド: out = bg*(1-alpha) + fg*alpha + // 256スケール正規化: alpha_256 = alpha + (alpha >> 7) + // 精度: 91.6%が完全一致、最大誤差±1 + uint_fast16_t alpha_256 = alpha + (alpha >> 7); + uint_fast16_t inv_a_256 = 256 - alpha_256; + uint32_t d32_even = d32 & 0x00FF00FF; + uint32_t s32_even = s32 & 0x00FF00FF; + uint32_t d32_odd = (d32 >> 8) & 0x00FF00FF; + uint32_t s32_odd = (s32 >> 8) & 0x00FF00FF; + d32_odd = d32_odd * inv_a_256 + s32_odd * alpha_256; + d32_even = d32_even * inv_a_256 + s32_even * alpha_256; + *reinterpret_cast(d) = d32_odd; + d[0] = static_cast(d32_even >> 8); + d[2] = static_cast(d32_even >> 24); + } while (--pixelCount > 0); + if (pixelCount <= 0) return; + if (alpha == 0) goto handle_alpha_0; + +handle_alpha_255: + // fgをコピー + *reinterpret_cast(d) = *reinterpret_cast(s); + if (--pixelCount <= 0) return; ++m; alpha = *m; d += 4; s += 4; - if (alpha == 0) - break; - if (alpha == 255) - break; - uint32_t d32 = *reinterpret_cast(d); - uint32_t s32 = *reinterpret_cast(s); - // fg/bg両方のブレンド: out = bg*(1-alpha) + fg*alpha - // 256スケール正規化: alpha_256 = alpha + (alpha >> 7) - // 精度: 91.6%が完全一致、最大誤差±1 - uint_fast16_t alpha_256 = alpha + (alpha >> 7); - uint_fast16_t inv_a_256 = 256 - alpha_256; - uint32_t d32_even = d32 & 0x00FF00FF; - uint32_t s32_even = s32 & 0x00FF00FF; - uint32_t d32_odd = (d32 >> 8) & 0x00FF00FF; - uint32_t s32_odd = (s32 >> 8) & 0x00FF00FF; - d32_odd = d32_odd * inv_a_256 + s32_odd * alpha_256; - d32_even = d32_even * inv_a_256 + s32_even * alpha_256; - *reinterpret_cast(d) = d32_odd; - d[0] = static_cast(d32_even >> 8); - d[2] = static_cast(d32_even >> 24); - } while (--pixelCount > 0); - if (pixelCount <= 0) - return; - if (alpha == 0) - goto handle_alpha_0; - -handle_alpha_255: - // fgをコピー - *reinterpret_cast(d) = *reinterpret_cast(s); - if (--pixelCount <= 0) - return; - ++m; - alpha = *m; - d += 4; - s += 4; - // 4px単位コピー - { - auto plimit = pixelCount >> 2; - if (plimit && alpha == 255 && (reinterpret_cast(m) & 3) == 0) { - uint32_t m32 = reinterpret_cast(m)[0]; - auto m_start = m; - do { - if (m32 != 0xFFFFFFFFu) - break; - m32 = reinterpret_cast(m)[1]; - m += 4; - } while (--plimit); - if (m != m_start) { - auto len = static_cast(m - m_start); - memcpy(d, s, static_cast(len) * 4); - pixelCount -= len; - if (pixelCount <= 0) - return; - alpha = static_cast(m32); - d += len * 4; - s += len * 4; - } + // 4px単位コピー + { + auto plimit = pixelCount >> 2; + if (plimit && alpha == 255 && (reinterpret_cast(m) & 3) == 0) { + uint32_t m32 = reinterpret_cast(m)[0]; + auto m_start = m; + do { + if (m32 != 0xFFFFFFFFu) break; + m32 = reinterpret_cast(m)[1]; + m += 4; + } while (--plimit); + if (m != m_start) { + auto len = static_cast(m - m_start); + memcpy(d, s, static_cast(len) * 4); + pixelCount -= len; + if (pixelCount <= 0) return; + alpha = static_cast(m32); + d += len * 4; + s += len * 4; + } + } } - } - if (alpha == 255) - goto handle_alpha_255; - if (alpha != 0) - goto blend; + if (alpha == 255) goto handle_alpha_255; + if (alpha != 0) goto blend; handle_alpha_0: - if (--pixelCount <= 0) - return; - ++m; - alpha = *m; - d += 4; - s += 4; - // 4px単位スキップ - { - auto plimit = pixelCount >> 2; - if (plimit && alpha == 0 && (reinterpret_cast(m) & 3) == 0) { - uint32_t m32 = reinterpret_cast(m)[0]; - auto m_start = m; - do { - if (m32 != 0) - break; - m32 = reinterpret_cast(m)[1]; - m += 4; - } while (--plimit); - if (m != m_start) { - auto skipped = static_cast(m - m_start); - pixelCount -= skipped; - if (pixelCount <= 0) - return; - alpha = static_cast(m32); - d += skipped * 4; - s += skipped * 4; - } + if (--pixelCount <= 0) return; + ++m; + alpha = *m; + d += 4; + s += 4; + // 4px単位スキップ + { + auto plimit = pixelCount >> 2; + if (plimit && alpha == 0 && (reinterpret_cast(m) & 3) == 0) { + uint32_t m32 = reinterpret_cast(m)[0]; + auto m_start = m; + do { + if (m32 != 0) break; + m32 = reinterpret_cast(m)[1]; + m += 4; + } while (--plimit); + if (m != m_start) { + auto skipped = static_cast(m - m_start); + pixelCount -= skipped; + if (pixelCount <= 0) return; + alpha = static_cast(m32); + d += skipped * 4; + s += skipped * 4; + } + } } - } - if (alpha == 0) - goto handle_alpha_0; - if (alpha == 255) - goto handle_alpha_255; - goto blend; + if (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; + goto blend; } // ---------------------------------------------------------------------------- -void MatteNode::applyMatteOverlay(ImageBuffer &output, int_fast16_t outWidth, - const InputView &fg, const InputView &mask) { - ViewPort outView = output.view(); - uint8_t *__restrict__ outData = static_cast(outView.data); - const auto outHeight = static_cast(outView.height); - const int32_t outStride = outView.stride; - - // マスクの有効X範囲(出力座標系) - const auto maskXStart = std::max(0, mask.offsetX); - const auto maskXEnd = - std::min(outWidth, mask.width + mask.offsetX); - if (maskXStart >= maskXEnd) - return; - - const auto maskSrcOffsetX = - static_cast(maskXStart - mask.offsetX); - - // 前景の有効X範囲(事前計算) - const auto fgXStart = - fg.valid() ? std::max(maskXStart, fg.offsetX) : maskXEnd; - const auto fgXEnd = - fg.valid() ? std::min(maskXEnd, fg.width + fg.offsetX) - : maskXStart; - const auto fgSrcOffsetX = static_cast(fgXStart - fg.offsetX); - - // 3領域の幅を事前計算 - const auto leftWidth = - static_cast(fgXStart - maskXStart); // 左領域(fgなし) - const auto midWidth = - static_cast(fgXEnd - fgXStart); // 中央領域(fg/bg両方) - const auto rightWidth = - static_cast(maskXEnd - fgXEnd); // 右領域(fgなし) - - // 行ごとに処理 - for (int_fast16_t y = 0; y < outHeight; ++y) { - // マスクがない行 → スキップ - const uint8_t *maskRowBase = mask.rowAt(y); - if (!maskRowBase) - continue; - - // ベースポインタ - const uint8_t *__restrict__ mBase = maskRowBase + maskSrcOffsetX; - uint8_t *__restrict__ dBase = outData + y * outStride + maskXStart * 4; - - // 左領域: fgなし - if (leftWidth > 0) { - processRowNoFg(dBase, mBase, static_cast(leftWidth)); - } +void MatteNode::applyMatteOverlay(ImageBuffer &output, int_fast16_t outWidth, const InputView &fg, + const InputView &mask) +{ + ViewPort outView = output.view(); + uint8_t *__restrict__ outData = static_cast(outView.data); + const auto outHeight = static_cast(outView.height); + const int32_t outStride = outView.stride; + + // マスクの有効X範囲(出力座標系) + const auto maskXStart = std::max(0, mask.offsetX); + const auto maskXEnd = std::min(outWidth, mask.width + mask.offsetX); + if (maskXStart >= maskXEnd) return; + + const auto maskSrcOffsetX = static_cast(maskXStart - mask.offsetX); + + // 前景の有効X範囲(事前計算) + const auto fgXStart = fg.valid() ? std::max(maskXStart, fg.offsetX) : maskXEnd; + const auto fgXEnd = fg.valid() ? std::min(maskXEnd, fg.width + fg.offsetX) : maskXStart; + const auto fgSrcOffsetX = static_cast(fgXStart - fg.offsetX); + + // 3領域の幅を事前計算 + const auto leftWidth = static_cast(fgXStart - maskXStart); // 左領域(fgなし) + const auto midWidth = static_cast(fgXEnd - fgXStart); // 中央領域(fg/bg両方) + const auto rightWidth = static_cast(maskXEnd - fgXEnd); // 右領域(fgなし) + + // 行ごとに処理 + for (int_fast16_t y = 0; y < outHeight; ++y) { + // マスクがない行 → スキップ + const uint8_t *maskRowBase = mask.rowAt(y); + if (!maskRowBase) continue; + + // ベースポインタ + const uint8_t *__restrict__ mBase = maskRowBase + maskSrcOffsetX; + uint8_t *__restrict__ dBase = outData + y * outStride + maskXStart * 4; + + // 左領域: fgなし + if (leftWidth > 0) { + processRowNoFg(dBase, mBase, static_cast(leftWidth)); + } - // 中央領域: fg/bg両方 - if (midWidth > 0) { - const uint8_t *fgRowBase = fg.rowAt(y); - if (fgRowBase) { - processRowWithFg(dBase + leftWidth * 4, mBase + leftWidth, - fgRowBase + fgSrcOffsetX * 4, - static_cast(midWidth)); - } - } + // 中央領域: fg/bg両方 + if (midWidth > 0) { + const uint8_t *fgRowBase = fg.rowAt(y); + if (fgRowBase) { + processRowWithFg(dBase + leftWidth * 4, mBase + leftWidth, fgRowBase + fgSrcOffsetX * 4, + static_cast(midWidth)); + } + } - // 右領域: fgなし - if (rightWidth > 0) { - processRowNoFg(dBase + (leftWidth + midWidth) * 4, - mBase + leftWidth + midWidth, - static_cast(rightWidth)); + // 右領域: fgなし + if (rightWidth > 0) { + processRowNoFg(dBase + (leftWidth + midWidth) * 4, mBase + leftWidth + midWidth, + static_cast(rightWidth)); + } } - } } #if defined(BENCH_M5STACK) || defined(BENCH_NATIVE) @@ -989,19 +906,19 @@ void MatteNode::applyMatteOverlay(ImageBuffer &output, int_fast16_t outWidth, // MatteNode - ベンチマーク用ラッパー関数 // ============================================================================ -void MatteNode::benchProcessRowWithFg(uint8_t *d, const uint8_t *m, - const uint8_t *s, int pixelCount) { - processRowWithFg(d, m, s, static_cast(pixelCount)); +void MatteNode::benchProcessRowWithFg(uint8_t *d, const uint8_t *m, const uint8_t *s, int pixelCount) +{ + processRowWithFg(d, m, s, static_cast(pixelCount)); } -void MatteNode::benchProcessRowNoFg(uint8_t *d, const uint8_t *m, - int pixelCount) { - processRowNoFg(d, m, static_cast(pixelCount)); +void MatteNode::benchProcessRowNoFg(uint8_t *d, const uint8_t *m, int pixelCount) +{ + processRowNoFg(d, m, static_cast(pixelCount)); } #endif -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_MATTE_NODE_H +#endif // FLEXIMG_MATTE_NODE_H diff --git a/src/fleximg/nodes/ninepatch_source_node.h b/src/fleximg/nodes/ninepatch_source_node.h index 8a0f3bc..7ef38c8 100644 --- a/src/fleximg/nodes/ninepatch_source_node.h +++ b/src/fleximg/nodes/ninepatch_source_node.h @@ -37,238 +37,263 @@ namespace FLEXIMG_NAMESPACE { class NinePatchSourceNode : public Node, public AffineCapability { public: - // コンストラクタ - NinePatchSourceNode() { - initPorts(0, 1); // 入力0、出力1(終端ノード) - } - - // ======================================== - // 初期化メソッド - // ======================================== - - // 通常画像 + 境界座標を明示指定(上級者向け/内部用) - // left/top/right/bottom: 各角の固定サイズ(ピクセル) - void setupWithBounds(const ViewPort &image, int_fast16_t left, - int_fast16_t top, int_fast16_t right, - int_fast16_t bottom) { - source_ = image; - srcLeft_ = static_cast(left); - srcTop_ = static_cast(top); - srcRight_ = static_cast(right); - srcBottom_ = static_cast(bottom); - // クリッピングなしの初期状態 - effectiveSrcLeft_ = static_cast(left); - effectiveSrcRight_ = static_cast(right); - effectiveSrcTop_ = static_cast(top); - effectiveSrcBottom_ = static_cast(bottom); - sourceValid_ = image.isValid(); - geometryValid_ = false; - - // 各区画のソースサイズを計算 - calcSrcPatchSizes(); - } - - // 9patch互換画像(外周1pxがメタデータ)を渡す(メインAPI) - // 外周1pxを解析して境界座標を自動取得、内部画像を抽出 - void setupFromNinePatch(const ViewPort &ninePatchImage) { - if (!ninePatchImage.isValid() || ninePatchImage.width < 3 || - ninePatchImage.height < 3) { - sourceValid_ = false; - return; - } - - // 黒ピクセル判定ラムダ(RGBA8_Straight: R=0, G=0, B=0, A>0) - auto isBlack = [&](int x, int y) -> bool { - const uint8_t *pixel = - static_cast(ninePatchImage.pixelAt(x, y)); - if (!pixel) - return false; - return pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 && pixel[3] > 0; - }; + // コンストラクタ + NinePatchSourceNode() + { + initPorts(0, 1); // 入力0、出力1(終端ノード) + } - // 内部画像(外周1pxを除く)を抽出 - ViewPort innerImage = - view_ops::subView(ninePatchImage, 1, 1, ninePatchImage.width - 2, - ninePatchImage.height - 2); - - // 上辺(y=0)のメタデータを解析 → 横方向の伸縮領域 - int_fast16_t stretchXStart = -1, stretchXEnd = -1; - for (int_fast16_t x = 1; x < ninePatchImage.width - 1; x++) { - if (isBlack(x, 0)) { - if (stretchXStart < 0) - stretchXStart = x - 1; // 外周を除いた座標 - stretchXEnd = x - 1; - } - } - - // 左辺(x=0)のメタデータを解析 → 縦方向の伸縮領域 - int_fast16_t stretchYStart = -1, stretchYEnd = -1; - for (int_fast16_t y = 1; y < ninePatchImage.height - 1; y++) { - if (isBlack(0, y)) { - if (stretchYStart < 0) - stretchYStart = y - 1; // 外周を除いた座標 - stretchYEnd = y - 1; - } - } - - // 境界座標を計算 - auto left = - static_cast((stretchXStart >= 0) ? (stretchXStart) : 0); - auto right = static_cast( - (stretchXEnd >= 0) ? (innerImage.width - 1 - stretchXEnd) : 0); - auto top = - static_cast((stretchYStart >= 0) ? (stretchYStart) : 0); - auto bottom = static_cast( - (stretchYEnd >= 0) ? (innerImage.height - 1 - stretchYEnd) : 0); - - // setupWithBounds を呼び出し - setupWithBounds(innerImage, left, top, right, bottom); - } - - // 出力サイズ設定(小数対応) - void setOutputSize(float width, float height) { - if (outputWidth_ != width || outputHeight_ != height) { - outputWidth_ = width; - outputHeight_ = height; - geometryValid_ = false; - } - } - - // 基準点設定(pivot: 画像内のアンカーポイント、デフォルトは左上 (0,0)) - void setPivot(int_fixed x, int_fixed y) { - if (pivotX_ != x || pivotY_ != y) { - pivotX_ = x; - pivotY_ = y; - geometryValid_ = false; // アフィン行列の再計算が必要 - } - } - - // 配置位置設定(アフィン行列のtx/tyに加算) - void setPosition(float x, float y) { - if (positionX_ != x || positionY_ != y) { - positionX_ = x; - positionY_ = y; - geometryValid_ = false; // アフィン行列の再計算が必要 - } - } - - // 補間モード設定(内部の全SourceNodeに適用) - void setInterpolationMode(InterpolationMode mode) { - if (interpolationMode_ != mode) { - interpolationMode_ = mode; - geometryValid_ = false; // ソースビュー再設定が必要 + // ======================================== + // 初期化メソッド + // ======================================== + + // 通常画像 + 境界座標を明示指定(上級者向け/内部用) + // left/top/right/bottom: 各角の固定サイズ(ピクセル) + void setupWithBounds(const ViewPort &image, int_fast16_t left, int_fast16_t top, int_fast16_t right, + int_fast16_t bottom) + { + source_ = image; + srcLeft_ = static_cast(left); + srcTop_ = static_cast(top); + srcRight_ = static_cast(right); + srcBottom_ = static_cast(bottom); + // クリッピングなしの初期状態 + effectiveSrcLeft_ = static_cast(left); + effectiveSrcRight_ = static_cast(right); + effectiveSrcTop_ = static_cast(top); + effectiveSrcBottom_ = static_cast(bottom); + sourceValid_ = image.isValid(); + geometryValid_ = false; + + // 各区画のソースサイズを計算 + calcSrcPatchSizes(); } - for (int i = 0; i < 9; i++) { - patches_[i].setInterpolationMode(mode); + + // 9patch互換画像(外周1pxがメタデータ)を渡す(メインAPI) + // 外周1pxを解析して境界座標を自動取得、内部画像を抽出 + void setupFromNinePatch(const ViewPort &ninePatchImage) + { + if (!ninePatchImage.isValid() || ninePatchImage.width < 3 || ninePatchImage.height < 3) { + sourceValid_ = false; + return; + } + + // 黒ピクセル判定ラムダ(RGBA8_Straight: R=0, G=0, B=0, A>0) + auto isBlack = [&](int x, int y) -> bool { + const uint8_t *pixel = static_cast(ninePatchImage.pixelAt(x, y)); + if (!pixel) return false; + return pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 && pixel[3] > 0; + }; + + // 内部画像(外周1pxを除く)を抽出 + ViewPort innerImage = + view_ops::subView(ninePatchImage, 1, 1, ninePatchImage.width - 2, ninePatchImage.height - 2); + + // 上辺(y=0)のメタデータを解析 → 横方向の伸縮領域 + int_fast16_t stretchXStart = -1, stretchXEnd = -1; + for (int_fast16_t x = 1; x < ninePatchImage.width - 1; x++) { + if (isBlack(x, 0)) { + if (stretchXStart < 0) stretchXStart = x - 1; // 外周を除いた座標 + stretchXEnd = x - 1; + } + } + + // 左辺(x=0)のメタデータを解析 → 縦方向の伸縮領域 + int_fast16_t stretchYStart = -1, stretchYEnd = -1; + for (int_fast16_t y = 1; y < ninePatchImage.height - 1; y++) { + if (isBlack(0, y)) { + if (stretchYStart < 0) stretchYStart = y - 1; // 外周を除いた座標 + stretchYEnd = y - 1; + } + } + + // 境界座標を計算 + auto left = static_cast((stretchXStart >= 0) ? (stretchXStart) : 0); + auto right = static_cast((stretchXEnd >= 0) ? (innerImage.width - 1 - stretchXEnd) : 0); + auto top = static_cast((stretchYStart >= 0) ? (stretchYStart) : 0); + auto bottom = static_cast((stretchYEnd >= 0) ? (innerImage.height - 1 - stretchYEnd) : 0); + + // setupWithBounds を呼び出し + setupWithBounds(innerImage, left, top, right, bottom); } - } - // ======================================== - // アクセサ - // ======================================== + // 出力サイズ設定(小数対応) + void setOutputSize(float width, float height) + { + if (outputWidth_ != width || outputHeight_ != height) { + outputWidth_ = width; + outputHeight_ = height; + geometryValid_ = false; + } + } - float outputWidth() const { return outputWidth_; } - float outputHeight() const { return outputHeight_; } - int_fixed pivotX() const { return pivotX_; } - int_fixed pivotY() const { return pivotY_; } + // 基準点設定(pivot: 画像内のアンカーポイント、デフォルトは左上 (0,0)) + void setPivot(int_fixed x, int_fixed y) + { + if (pivotX_ != x || pivotY_ != y) { + pivotX_ = x; + pivotY_ = y; + geometryValid_ = false; // アフィン行列の再計算が必要 + } + } - // 境界座標(読み取り用) - int_fast16_t srcLeft() const { return srcLeft_; } - int_fast16_t srcTop() const { return srcTop_; } - int_fast16_t srcRight() const { return srcRight_; } - int_fast16_t srcBottom() const { return srcBottom_; } + // 配置位置設定(アフィン行列のtx/tyに加算) + void setPosition(float x, float y) + { + if (positionX_ != x || positionY_ != y) { + positionX_ = x; + positionY_ = y; + geometryValid_ = false; // アフィン行列の再計算が必要 + } + } - const char *name() const override { return "NinePatchSourceNode"; } - int nodeTypeForMetrics() const override { return NodeType::NinePatch; } + // 補間モード設定(内部の全SourceNodeに適用) + void setInterpolationMode(InterpolationMode mode) + { + if (interpolationMode_ != mode) { + interpolationMode_ = mode; + geometryValid_ = false; // ソースビュー再設定が必要 + } + for (int i = 0; i < 9; i++) { + patches_[i].setInterpolationMode(mode); + } + } - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // アクセサ + // ======================================== - // onPullPrepare: 各区画のSourceNodeにPrepareRequestを伝播 - PrepareResponse onPullPrepare(const PrepareRequest &request) override; + float outputWidth() const + { + return outputWidth_; + } + float outputHeight() const + { + return outputHeight_; + } + int_fixed pivotX() const + { + return pivotX_; + } + int_fixed pivotY() const + { + return pivotY_; + } - // onPullFinalize: 各区画のSourceNodeに終了を伝播 - void onPullFinalize() override; + // 境界座標(読み取り用) + int_fast16_t srcLeft() const + { + return srcLeft_; + } + int_fast16_t srcTop() const + { + return srcTop_; + } + int_fast16_t srcRight() const + { + return srcRight_; + } + int_fast16_t srcBottom() const + { + return srcBottom_; + } - // onPullProcess: 全9区画を処理して合成 - RenderResponse &onPullProcess(const RenderRequest &request) override; + const char *name() const override + { + return "NinePatchSourceNode"; + } + int nodeTypeForMetrics() const override + { + return NodeType::NinePatch; + } - // getDataRange: 全パッチのデータ範囲の和集合を返す - DataRange getDataRange(const RenderRequest &request) const override; + // ======================================== + // Template Method フック + // ======================================== + + // onPullPrepare: 各区画のSourceNodeにPrepareRequestを伝播 + PrepareResponse onPullPrepare(const PrepareRequest &request) override; + + // onPullFinalize: 各区画のSourceNodeに終了を伝播 + void onPullFinalize() override; + + // onPullProcess: 全9区画を処理して合成 + RenderResponse &onPullProcess(const RenderRequest &request) override; + + // getDataRange: 全パッチのデータ範囲の和集合を返す + DataRange getDataRange(const RenderRequest &request) const override; private: - // 内部SourceNode(9区画) - SourceNode patches_[9]; - - // 元画像 - ViewPort source_; - bool sourceValid_ = false; - - // 区画境界(ソース座標) - int16_t srcLeft_ = 0; // 左端からの固定幅 - int16_t srcTop_ = 0; // 上端からの固定高さ - int16_t srcRight_ = 0; // 右端からの固定幅 - int16_t srcBottom_ = 0; // 下端からの固定高さ - - // クリッピング適用後の固定部サイズ(出力サイズが固定部合計より小さい場合に使用) - int16_t effectiveSrcLeft_ = 0; - int16_t effectiveSrcRight_ = 0; - int16_t effectiveSrcTop_ = 0; - int16_t effectiveSrcBottom_ = 0; - - // 出力サイズ(小数対応) - float outputWidth_ = 0.0f; - float outputHeight_ = 0.0f; - - // 基準点(pivot: 回転・配置の中心、出力座標系) - int_fixed pivotX_ = 0; - int_fixed pivotY_ = 0; - - // 配置位置(アフィン行列のtx/tyに加算) - float positionX_ = 0.0f; - float positionY_ = 0.0f; - - // 補間モード - InterpolationMode interpolationMode_ = InterpolationMode::Nearest; - - // ジオメトリ計算結果(小数対応) - bool geometryValid_ = false; - float patchWidths_[3] = {0, 0, 0}; // [左固定, 中央伸縮, 右固定] - float patchHeights_[3] = {0, 0, 0}; // [上固定, 中央伸縮, 下固定] - float patchOffsetX_[3] = {0, 0, 0}; // 各列の出力X開始位置 - float patchOffsetY_[3] = {0, 0, 0}; // 各行の出力Y開始位置 - - // ソース画像内の各区画のサイズ - int16_t srcPatchW_[3] = {0, 0, 0}; // 各列のソース幅 - int16_t srcPatchH_[3] = {0, 0, 0}; // 各行のソース高さ - - // 各区画のスケール行列(伸縮用) - AffineMatrix patchScales_[9]; - bool patchNeedsAffine_[9] = {false}; // スケールが1.0でない場合true - - // ======================================== - // 内部メソッド - // ======================================== - - int_fast16_t getPatchIndex(int_fast16_t col, int_fast16_t row) const { - return row * 3 + col; - } - - // 各区画のソースサイズを計算(初期化時に呼び出し) - void calcSrcPatchSizes(); - - // 1軸方向のクリッピング計算(横/縦共通) - void calcAxisClipping(float outputSize, int_fast16_t srcFixed0, - int_fast16_t srcFixed2, float &outWidth0, - float &outWidth1, float &outWidth2, int16_t &effSrc0, - int16_t &effSrc2); - - // 出力サイズ変更時にジオメトリを再計算 - void updatePatchGeometry(); + // 内部SourceNode(9区画) + SourceNode patches_[9]; + + // 元画像 + ViewPort source_; + bool sourceValid_ = false; + + // 区画境界(ソース座標) + int16_t srcLeft_ = 0; // 左端からの固定幅 + int16_t srcTop_ = 0; // 上端からの固定高さ + int16_t srcRight_ = 0; // 右端からの固定幅 + int16_t srcBottom_ = 0; // 下端からの固定高さ + + // クリッピング適用後の固定部サイズ(出力サイズが固定部合計より小さい場合に使用) + int16_t effectiveSrcLeft_ = 0; + int16_t effectiveSrcRight_ = 0; + int16_t effectiveSrcTop_ = 0; + int16_t effectiveSrcBottom_ = 0; + + // 出力サイズ(小数対応) + float outputWidth_ = 0.0f; + float outputHeight_ = 0.0f; + + // 基準点(pivot: 回転・配置の中心、出力座標系) + int_fixed pivotX_ = 0; + int_fixed pivotY_ = 0; + + // 配置位置(アフィン行列のtx/tyに加算) + float positionX_ = 0.0f; + float positionY_ = 0.0f; + + // 補間モード + InterpolationMode interpolationMode_ = InterpolationMode::Nearest; + + // ジオメトリ計算結果(小数対応) + bool geometryValid_ = false; + float patchWidths_[3] = {0, 0, 0}; // [左固定, 中央伸縮, 右固定] + float patchHeights_[3] = {0, 0, 0}; // [上固定, 中央伸縮, 下固定] + float patchOffsetX_[3] = {0, 0, 0}; // 各列の出力X開始位置 + float patchOffsetY_[3] = {0, 0, 0}; // 各行の出力Y開始位置 + + // ソース画像内の各区画のサイズ + int16_t srcPatchW_[3] = {0, 0, 0}; // 各列のソース幅 + int16_t srcPatchH_[3] = {0, 0, 0}; // 各行のソース高さ + + // 各区画のスケール行列(伸縮用) + AffineMatrix patchScales_[9]; + bool patchNeedsAffine_[9] = {false}; // スケールが1.0でない場合true + + // ======================================== + // 内部メソッド + // ======================================== + + int_fast16_t getPatchIndex(int_fast16_t col, int_fast16_t row) const + { + return row * 3 + col; + } + + // 各区画のソースサイズを計算(初期化時に呼び出し) + void calcSrcPatchSizes(); + + // 1軸方向のクリッピング計算(横/縦共通) + void calcAxisClipping(float outputSize, int_fast16_t srcFixed0, int_fast16_t srcFixed2, float &outWidth0, + float &outWidth1, float &outWidth2, int16_t &effSrc0, int16_t &effSrc2); + + // 出力サイズ変更時にジオメトリを再計算 + void updatePatchGeometry(); }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -281,376 +306,354 @@ namespace FLEXIMG_NAMESPACE { // NinePatchSourceNode - Template Method フック実装 // ============================================================================ -PrepareResponse -NinePatchSourceNode::onPullPrepare(const PrepareRequest &request) { - // ジオメトリ計算(まだなら) - if (!geometryValid_) { - updatePatchGeometry(); - } - - // AffineCapability: 自身のlocalMatrix_をrequest.affineMatrixと合成 - AffineMatrix combinedAffine; - bool hasCombinedAffine = false; - if (hasLocalTransform()) { - if (request.hasAffine) { - combinedAffine = request.affineMatrix * localMatrix_; +PrepareResponse NinePatchSourceNode::onPullPrepare(const PrepareRequest &request) +{ + // ジオメトリ計算(まだなら) + if (!geometryValid_) { + updatePatchGeometry(); + } + + // AffineCapability: 自身のlocalMatrix_をrequest.affineMatrixと合成 + AffineMatrix combinedAffine; + bool hasCombinedAffine = false; + if (hasLocalTransform()) { + if (request.hasAffine) { + combinedAffine = request.affineMatrix * localMatrix_; + } else { + combinedAffine = localMatrix_; + } + hasCombinedAffine = true; + } else if (request.hasAffine) { + combinedAffine = request.affineMatrix; + hasCombinedAffine = true; + } + + // 各区画のSourceNodeにPrepareRequestを伝播(スケール行列付き) + for (int i = 0; i < 9; i++) { + PrepareRequest patchRequest = request; + patchRequest.hasAffine = hasCombinedAffine; + patchRequest.affineMatrix = combinedAffine; + + // 親のアフィン行列と区画のスケール行列を合成 + if (patchNeedsAffine_[i]) { + if (hasCombinedAffine) { + // 親アフィン × 区画スケール + patchRequest.affineMatrix = combinedAffine * patchScales_[i]; + } else { + patchRequest.affineMatrix = patchScales_[i]; + } + patchRequest.hasAffine = true; + } + // patchNeedsAffine_[i] == false の場合、親のアフィンをそのまま使用 + + patches_[i].pullPrepare(patchRequest); + } + + // NinePatchSourceNodeは終端なので上流への伝播なし + // プルアフィン変換がある場合、出力側で必要なAABBを計算 + PrepareResponse result; + result.status = PrepareStatus::Prepared; + result.preferredFormat = source_.formatID; + + if (hasCombinedAffine) { + // positionを含めた行列を計算 + AffineMatrix matrixWithPos = combinedAffine; + float transformedPosX = matrixWithPos.a * positionX_ + matrixWithPos.b * positionY_; + float transformedPosY = matrixWithPos.c * positionX_ + matrixWithPos.d * positionY_; + matrixWithPos.tx += transformedPosX; + matrixWithPos.ty += transformedPosY; + + // 出力矩形に順変換を適用して出力側のAABBを計算 + calcAffineAABB(static_cast(outputWidth_), static_cast(outputHeight_), {pivotX_, pivotY_}, + matrixWithPos, result.width, result.height, result.origin); } else { - combinedAffine = localMatrix_; - } - hasCombinedAffine = true; - } else if (request.hasAffine) { - combinedAffine = request.affineMatrix; - hasCombinedAffine = true; - } - - // 各区画のSourceNodeにPrepareRequestを伝播(スケール行列付き) - for (int i = 0; i < 9; i++) { - PrepareRequest patchRequest = request; - patchRequest.hasAffine = hasCombinedAffine; - patchRequest.affineMatrix = combinedAffine; - - // 親のアフィン行列と区画のスケール行列を合成 - if (patchNeedsAffine_[i]) { - if (hasCombinedAffine) { - // 親アフィン × 区画スケール - patchRequest.affineMatrix = combinedAffine * patchScales_[i]; - } else { - patchRequest.affineMatrix = patchScales_[i]; - } - patchRequest.hasAffine = true; - } - // patchNeedsAffine_[i] == false の場合、親のアフィンをそのまま使用 - - patches_[i].pullPrepare(patchRequest); - } - - // NinePatchSourceNodeは終端なので上流への伝播なし - // プルアフィン変換がある場合、出力側で必要なAABBを計算 - PrepareResponse result; - result.status = PrepareStatus::Prepared; - result.preferredFormat = source_.formatID; - - if (hasCombinedAffine) { - // positionを含めた行列を計算 - AffineMatrix matrixWithPos = combinedAffine; - float transformedPosX = - matrixWithPos.a * positionX_ + matrixWithPos.b * positionY_; - float transformedPosY = - matrixWithPos.c * positionX_ + matrixWithPos.d * positionY_; - matrixWithPos.tx += transformedPosX; - matrixWithPos.ty += transformedPosY; - - // 出力矩形に順変換を適用して出力側のAABBを計算 - calcAffineAABB(static_cast(outputWidth_), - static_cast(outputHeight_), {pivotX_, pivotY_}, - matrixWithPos, result.width, result.height, result.origin); - } else { - // アフィンなしの場合はそのまま(positionを含める) - result.width = static_cast(outputWidth_); - result.height = static_cast(outputHeight_); - // 新座標系: originはバッファ左上のワールド座標 - // position - origin = バッファ[0,0]のワールド座標 - result.origin.x = float_to_fixed(positionX_) - pivotX_; - result.origin.y = float_to_fixed(positionY_) - pivotY_; - } - return result; + // アフィンなしの場合はそのまま(positionを含める) + result.width = static_cast(outputWidth_); + result.height = static_cast(outputHeight_); + // 新座標系: originはバッファ左上のワールド座標 + // position - origin = バッファ[0,0]のワールド座標 + result.origin.x = float_to_fixed(positionX_) - pivotX_; + result.origin.y = float_to_fixed(positionY_) - pivotY_; + } + return result; } -void NinePatchSourceNode::onPullFinalize() { - for (int i = 0; i < 9; i++) { - patches_[i].pullFinalize(); - } - finalize(); +void NinePatchSourceNode::onPullFinalize() +{ + for (int i = 0; i < 9; i++) { + patches_[i].pullFinalize(); + } + finalize(); } -RenderResponse & -NinePatchSourceNode::onPullProcess(const RenderRequest &request) { - if (!sourceValid_ || outputWidth_ <= 0 || outputHeight_ <= 0) { - return makeEmptyResponse(request.origin); - } - - // ジオメトリ計算(まだなら) - if (!geometryValid_) { - updatePatchGeometry(); - } - - // 描画順序: 伸縮パッチ → 固定パッチ - // オーバーラップ領域で固定パッチが伸縮パッチの上に描画される - // (バイリニア時のエッジを目立たなくするため) - constexpr uint8_t drawOrder[9] = { - 4, // 中央パッチ(両方向伸縮)を最初に - 1, 3, 5, 7, // 伸縮パッチ(辺) - 0, 2, 6, 8 // 固定パッチ(角)を最後に - }; - - // キャンバス範囲を計算(全パッチのDataRangeを集計) - int_fast16_t canvasStartX = INT16_MAX; - int_fast16_t canvasEndX = 0; - for (auto i : drawOrder) { - auto col = static_cast(i % 3); - auto row = static_cast(i / 3); - // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) - if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { - continue; - } - DataRange range = patches_[i].getDataRange(request); - if (range.hasData()) { - if (range.startX < canvasStartX) - canvasStartX = range.startX; - if (range.endX > canvasEndX) - canvasEndX = range.endX; - } - } - - // 有効なデータがない場合は空を返す - if (canvasStartX >= canvasEndX) { - return makeEmptyResponse(request.origin); - } - - int_fast16_t canvasWidth = canvasEndX - canvasStartX; - - // キャンバス作成(透明で初期化、必要幅のみ確保) - int_fixed canvasOriginX = request.origin.x + to_fixed(canvasStartX); - int_fixed canvasOriginY = request.origin.y; - - ImageBuffer canvasBuf = canvas_utils::createCanvas( - canvasWidth, request.height, InitPolicy::Zero, allocator()); - ViewPort canvasView = canvasBuf.view(); - - // 全9区画を処理 - for (auto i : drawOrder) { - // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) - auto col = static_cast(i % 3); - auto row = static_cast(i / 3); - if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { - continue; - } - - // 範囲外のパッチはスキップ - DataRange range = patches_[i].getDataRange(request); - if (!range.hasData()) - continue; - - RenderResponse &patchResult = patches_[i].pullProcess(request); - if (!patchResult.isValid()) { - context_->releaseResponse(patchResult); - continue; - } - - // フォーマット変換 - canvas_utils::ensureBlendableFormat(patchResult); - - // キャンバスに配置(全パッチ上書き) - // NinePatchではパッチ同士の重なりは単純上書きで良い(EdgeFadeFlagsにより内部エッジはフェードアウトしない) - canvas_utils::placeFirst(canvasView, canvasOriginX, canvasOriginY, - patchResult.view(), patchResult.origin.x, - patchResult.origin.y); - - // 使い終わったRenderResponseをプールに返却 - context_->releaseResponse(patchResult); - } - - return makeResponse(std::move(canvasBuf), - Point{canvasOriginX, canvasOriginY}); +RenderResponse &NinePatchSourceNode::onPullProcess(const RenderRequest &request) +{ + if (!sourceValid_ || outputWidth_ <= 0 || outputHeight_ <= 0) { + return makeEmptyResponse(request.origin); + } + + // ジオメトリ計算(まだなら) + if (!geometryValid_) { + updatePatchGeometry(); + } + + // 描画順序: 伸縮パッチ → 固定パッチ + // オーバーラップ領域で固定パッチが伸縮パッチの上に描画される + // (バイリニア時のエッジを目立たなくするため) + constexpr uint8_t drawOrder[9] = { + 4, // 中央パッチ(両方向伸縮)を最初に + 1, 3, 5, 7, // 伸縮パッチ(辺) + 0, 2, 6, 8 // 固定パッチ(角)を最後に + }; + + // キャンバス範囲を計算(全パッチのDataRangeを集計) + int_fast16_t canvasStartX = INT16_MAX; + int_fast16_t canvasEndX = 0; + for (auto i : drawOrder) { + auto col = static_cast(i % 3); + auto row = static_cast(i / 3); + // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) + if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { + continue; + } + DataRange range = patches_[i].getDataRange(request); + if (range.hasData()) { + if (range.startX < canvasStartX) canvasStartX = range.startX; + if (range.endX > canvasEndX) canvasEndX = range.endX; + } + } + + // 有効なデータがない場合は空を返す + if (canvasStartX >= canvasEndX) { + return makeEmptyResponse(request.origin); + } + + int_fast16_t canvasWidth = canvasEndX - canvasStartX; + + // キャンバス作成(透明で初期化、必要幅のみ確保) + int_fixed canvasOriginX = request.origin.x + to_fixed(canvasStartX); + int_fixed canvasOriginY = request.origin.y; + + ImageBuffer canvasBuf = canvas_utils::createCanvas(canvasWidth, request.height, InitPolicy::Zero, allocator()); + ViewPort canvasView = canvasBuf.view(); + + // 全9区画を処理 + for (auto i : drawOrder) { + // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) + auto col = static_cast(i % 3); + auto row = static_cast(i / 3); + if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { + continue; + } + + // 範囲外のパッチはスキップ + DataRange range = patches_[i].getDataRange(request); + if (!range.hasData()) continue; + + RenderResponse &patchResult = patches_[i].pullProcess(request); + if (!patchResult.isValid()) { + context_->releaseResponse(patchResult); + continue; + } + + // フォーマット変換 + canvas_utils::ensureBlendableFormat(patchResult); + + // キャンバスに配置(全パッチ上書き) + // NinePatchではパッチ同士の重なりは単純上書きで良い(EdgeFadeFlagsにより内部エッジはフェードアウトしない) + canvas_utils::placeFirst(canvasView, canvasOriginX, canvasOriginY, patchResult.view(), patchResult.origin.x, + patchResult.origin.y); + + // 使い終わったRenderResponseをプールに返却 + context_->releaseResponse(patchResult); + } + + return makeResponse(std::move(canvasBuf), Point{canvasOriginX, canvasOriginY}); } -DataRange -NinePatchSourceNode::getDataRange(const RenderRequest &request) const { - if (!sourceValid_ || outputWidth_ <= 0 || outputHeight_ <= 0) { - return DataRange{0, 0}; - } - - // ジオメトリ計算(まだなら) - if (!geometryValid_) { - const_cast(this)->updatePatchGeometry(); - } - - // 全パッチのデータ範囲の和集合を計算 - int_fast16_t startX = INT16_MAX; - int_fast16_t endX = INT16_MIN; - - for (int i = 0; i < 9; i++) { - auto col = static_cast(i % 3); - auto row = static_cast(i / 3); - // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) - if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { - continue; - } - DataRange range = patches_[i].getDataRange(request); - if (range.hasData()) { - if (range.startX < startX) - startX = range.startX; - if (range.endX > endX) - endX = range.endX; - } - } - - if (startX >= endX) { - return DataRange{0, 0}; - } - return DataRange{static_cast(startX), static_cast(endX)}; +DataRange NinePatchSourceNode::getDataRange(const RenderRequest &request) const +{ + if (!sourceValid_ || outputWidth_ <= 0 || outputHeight_ <= 0) { + return DataRange{0, 0}; + } + + // ジオメトリ計算(まだなら) + if (!geometryValid_) { + const_cast(this)->updatePatchGeometry(); + } + + // 全パッチのデータ範囲の和集合を計算 + int_fast16_t startX = INT16_MAX; + int_fast16_t endX = INT16_MIN; + + for (int i = 0; i < 9; i++) { + auto col = static_cast(i % 3); + auto row = static_cast(i / 3); + // ソースサイズが0のパッチはスキップ(伸縮パッチはマイナス出力でも描画) + if (srcPatchW_[col] <= 0 || srcPatchH_[row] <= 0) { + continue; + } + DataRange range = patches_[i].getDataRange(request); + if (range.hasData()) { + if (range.startX < startX) startX = range.startX; + if (range.endX > endX) endX = range.endX; + } + } + + if (startX >= endX) { + return DataRange{0, 0}; + } + return DataRange{static_cast(startX), static_cast(endX)}; } // ============================================================================ // NinePatchSourceNode - private ヘルパーメソッド実装 // ============================================================================ -void NinePatchSourceNode::calcSrcPatchSizes() { - srcPatchW_[0] = srcLeft_; - srcPatchW_[1] = source_.width - srcLeft_ - srcRight_; - srcPatchW_[2] = srcRight_; - srcPatchH_[0] = srcTop_; - srcPatchH_[1] = source_.height - srcTop_ - srcBottom_; - srcPatchH_[2] = srcBottom_; +void NinePatchSourceNode::calcSrcPatchSizes() +{ + srcPatchW_[0] = srcLeft_; + srcPatchW_[1] = source_.width - srcLeft_ - srcRight_; + srcPatchW_[2] = srcRight_; + srcPatchH_[0] = srcTop_; + srcPatchH_[1] = source_.height - srcTop_ - srcBottom_; + srcPatchH_[2] = srcBottom_; } -void NinePatchSourceNode::calcAxisClipping(float outputSize, - int_fast16_t srcFixed0, - int_fast16_t srcFixed2, - float &outWidth0, float &outWidth1, - float &outWidth2, int16_t &effSrc0, - int16_t &effSrc2) { - // ソースサイズは常に元のまま - effSrc0 = static_cast(srcFixed0); - effSrc2 = static_cast(srcFixed2); - - float totalFixed = static_cast(srcFixed0 + srcFixed2); - if (outputSize < totalFixed && totalFixed > 0) { - // 全体幅が固定部合計より小さい場合:比率で按分(はみ出し防止) - float ratio = outputSize / totalFixed; - outWidth0 = static_cast(srcFixed0) * ratio; - outWidth2 = static_cast(srcFixed2) * ratio; - outWidth1 = 0.0f; // 伸縮部は0(位置は左右固定の境界) - } else { - // 通常時:固定部はソースサイズ、伸縮部はマイナスも許容 - outWidth0 = static_cast(srcFixed0); - outWidth2 = static_cast(srcFixed2); - outWidth1 = outputSize - outWidth0 - outWidth2; - } +void NinePatchSourceNode::calcAxisClipping(float outputSize, int_fast16_t srcFixed0, int_fast16_t srcFixed2, + float &outWidth0, float &outWidth1, float &outWidth2, int16_t &effSrc0, + int16_t &effSrc2) +{ + // ソースサイズは常に元のまま + effSrc0 = static_cast(srcFixed0); + effSrc2 = static_cast(srcFixed2); + + float totalFixed = static_cast(srcFixed0 + srcFixed2); + if (outputSize < totalFixed && totalFixed > 0) { + // 全体幅が固定部合計より小さい場合:比率で按分(はみ出し防止) + float ratio = outputSize / totalFixed; + outWidth0 = static_cast(srcFixed0) * ratio; + outWidth2 = static_cast(srcFixed2) * ratio; + outWidth1 = 0.0f; // 伸縮部は0(位置は左右固定の境界) + } else { + // 通常時:固定部はソースサイズ、伸縮部はマイナスも許容 + outWidth0 = static_cast(srcFixed0); + outWidth2 = static_cast(srcFixed2); + outWidth1 = outputSize - outWidth0 - outWidth2; + } } -void NinePatchSourceNode::updatePatchGeometry() { - if (!sourceValid_) - return; - - // 横方向・縦方向のクリッピング計算 - calcAxisClipping(outputWidth_, srcLeft_, srcRight_, patchWidths_[0], - patchWidths_[1], patchWidths_[2], effectiveSrcLeft_, - effectiveSrcRight_); - calcAxisClipping(outputHeight_, srcTop_, srcBottom_, patchHeights_[0], - patchHeights_[1], patchHeights_[2], effectiveSrcTop_, - effectiveSrcBottom_); - - // 各区画の出力開始位置 - patchOffsetX_[0] = 0.0f; - patchOffsetX_[1] = patchWidths_[0]; - patchOffsetX_[2] = outputWidth_ - patchWidths_[2]; - patchOffsetY_[0] = 0.0f; - patchOffsetY_[1] = patchHeights_[0]; - patchOffsetY_[2] = outputHeight_ - patchHeights_[2]; - - // 各列/行の有効ソースサイズと開始位置 - int16_t effW[3] = {effectiveSrcLeft_, srcPatchW_[1], effectiveSrcRight_}; - int16_t effH[3] = {effectiveSrcTop_, srcPatchH_[1], effectiveSrcBottom_}; - int16_t srcX[3] = {0, srcLeft_, - static_cast(source_.width - effectiveSrcRight_)}; - int16_t srcY[3] = { - 0, srcTop_, static_cast(source_.height - effectiveSrcBottom_)}; - - // オーバーラップ有効判定(伸縮部の出力サイズが1以上の場合のみ) - // 伸縮部が1未満になったらオーバーラップをオフにする - bool hasHStretch = effW[1] > 0 && patchWidths_[1] >= 1.0f; - bool hasVStretch = effH[1] > 0 && patchHeights_[1] >= 1.0f; - - float pivotXf = static_cast(pivotX_) / INT_FIXED_ONE; - float pivotYf = static_cast(pivotY_) / INT_FIXED_ONE; - - for (int row = 0; row < 3; row++) { - for (int col = 0; col < 3; col++) { - int idx = row * 3 + col; - - // オーバーラップ量(固定部→伸縮部方向に拡張) - int_fast16_t dx = 0, dy = 0, dw = 0, dh = 0; - - // 横方向オーバーラップ(伸縮パッチがある場合、固定部を伸縮部側に拡張) - if (hasHStretch) { - if (col == 0 && effW[0] > 0) { - dw = 1; - } // 左固定: 右に拡張 - else if (col == 2 && effW[2] > 0) { - dx = -1; - dw = 1; - } // 右固定: 左に拡張 - } - - // 縦方向オーバーラップ(伸縮パッチがある場合、固定部を伸縮部側に拡張) - if (hasVStretch) { - if (row == 0 && effH[0] > 0) { - dh = 1; - } // 上固定: 下に拡張 - else if (row == 2 && effH[2] > 0) { - dy = -1; - dh = 1; - } // 下固定: 上に拡張 - } - - // ソースビュー設定 - if (effW[col] > 0 && effH[row] > 0) { - ViewPort subView = - view_ops::subView(source_, srcX[col] + dx, srcY[row] + dy, - effW[col] + dw, effH[row] + dh); - patches_[idx].setSource(subView); - patches_[idx].setPivot(0, 0); - - // エッジフェードアウト設定(外周の辺のみフェードアウト有効) - // 隣接パッチとの境界(内部の辺)はフェードアウト無効 - uint8_t edgeFade = EdgeFade_None; - if (row == 0) - edgeFade |= EdgeFade_Top; // 上端パッチ: 上辺フェード有効 - if (row == 2) - edgeFade |= EdgeFade_Bottom; // 下端パッチ: 下辺フェード有効 - if (col == 0) - edgeFade |= EdgeFade_Left; // 左端パッチ: 左辺フェード有効 - if (col == 2) - edgeFade |= EdgeFade_Right; // 右端パッチ: 右辺フェード有効 - patches_[idx].setEdgeFade(edgeFade); - } - - // スケール計算(出力サイズ / ソースサイズ) - float scaleX = 1.0f, scaleY = 1.0f; - - // 横方向スケール - if (srcPatchW_[col] > 0) { - scaleX = patchWidths_[col] / static_cast(srcPatchW_[col]); - } - - // 縦方向スケール - if (srcPatchH_[row] > 0) { - scaleY = patchHeights_[row] / static_cast(srcPatchH_[row]); - } - - // 平行移動量 - float tx = - patchOffsetX_[col] + static_cast(dx) - pivotXf + positionX_; - float ty = - patchOffsetY_[row] + static_cast(dy) - pivotYf + positionY_; - - // バイリニア時の伸縮部位置補正 - // if (interpolationMode_ == InterpolationMode::Bilinear) { - // if (col == 1 && srcPatchW_[1] > 1) tx -= scaleX * 0.5f; - // if (row == 1 && srcPatchH_[1] > 1) ty -= scaleY * 0.5f; - // } - - patchScales_[idx] = AffineMatrix(scaleX, 0.0f, 0.0f, scaleY, tx, ty); - patchNeedsAffine_[idx] = true; - } - } - - geometryValid_ = true; +void NinePatchSourceNode::updatePatchGeometry() +{ + if (!sourceValid_) return; + + // 横方向・縦方向のクリッピング計算 + calcAxisClipping(outputWidth_, srcLeft_, srcRight_, patchWidths_[0], patchWidths_[1], patchWidths_[2], + effectiveSrcLeft_, effectiveSrcRight_); + calcAxisClipping(outputHeight_, srcTop_, srcBottom_, patchHeights_[0], patchHeights_[1], patchHeights_[2], + effectiveSrcTop_, effectiveSrcBottom_); + + // 各区画の出力開始位置 + patchOffsetX_[0] = 0.0f; + patchOffsetX_[1] = patchWidths_[0]; + patchOffsetX_[2] = outputWidth_ - patchWidths_[2]; + patchOffsetY_[0] = 0.0f; + patchOffsetY_[1] = patchHeights_[0]; + patchOffsetY_[2] = outputHeight_ - patchHeights_[2]; + + // 各列/行の有効ソースサイズと開始位置 + int16_t effW[3] = {effectiveSrcLeft_, srcPatchW_[1], effectiveSrcRight_}; + int16_t effH[3] = {effectiveSrcTop_, srcPatchH_[1], effectiveSrcBottom_}; + int16_t srcX[3] = {0, srcLeft_, static_cast(source_.width - effectiveSrcRight_)}; + int16_t srcY[3] = {0, srcTop_, static_cast(source_.height - effectiveSrcBottom_)}; + + // オーバーラップ有効判定(伸縮部の出力サイズが1以上の場合のみ) + // 伸縮部が1未満になったらオーバーラップをオフにする + bool hasHStretch = effW[1] > 0 && patchWidths_[1] >= 1.0f; + bool hasVStretch = effH[1] > 0 && patchHeights_[1] >= 1.0f; + + float pivotXf = static_cast(pivotX_) / INT_FIXED_ONE; + float pivotYf = static_cast(pivotY_) / INT_FIXED_ONE; + + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 3; col++) { + int idx = row * 3 + col; + + // オーバーラップ量(固定部→伸縮部方向に拡張) + int_fast16_t dx = 0, dy = 0, dw = 0, dh = 0; + + // 横方向オーバーラップ(伸縮パッチがある場合、固定部を伸縮部側に拡張) + if (hasHStretch) { + if (col == 0 && effW[0] > 0) { + dw = 1; + } // 左固定: 右に拡張 + else if (col == 2 && effW[2] > 0) { + dx = -1; + dw = 1; + } // 右固定: 左に拡張 + } + + // 縦方向オーバーラップ(伸縮パッチがある場合、固定部を伸縮部側に拡張) + if (hasVStretch) { + if (row == 0 && effH[0] > 0) { + dh = 1; + } // 上固定: 下に拡張 + else if (row == 2 && effH[2] > 0) { + dy = -1; + dh = 1; + } // 下固定: 上に拡張 + } + + // ソースビュー設定 + if (effW[col] > 0 && effH[row] > 0) { + ViewPort subView = + view_ops::subView(source_, srcX[col] + dx, srcY[row] + dy, effW[col] + dw, effH[row] + dh); + patches_[idx].setSource(subView); + patches_[idx].setPivot(0, 0); + + // エッジフェードアウト設定(外周の辺のみフェードアウト有効) + // 隣接パッチとの境界(内部の辺)はフェードアウト無効 + uint8_t edgeFade = EdgeFade_None; + if (row == 0) edgeFade |= EdgeFade_Top; // 上端パッチ: 上辺フェード有効 + if (row == 2) edgeFade |= EdgeFade_Bottom; // 下端パッチ: 下辺フェード有効 + if (col == 0) edgeFade |= EdgeFade_Left; // 左端パッチ: 左辺フェード有効 + if (col == 2) edgeFade |= EdgeFade_Right; // 右端パッチ: 右辺フェード有効 + patches_[idx].setEdgeFade(edgeFade); + } + + // スケール計算(出力サイズ / ソースサイズ) + float scaleX = 1.0f, scaleY = 1.0f; + + // 横方向スケール + if (srcPatchW_[col] > 0) { + scaleX = patchWidths_[col] / static_cast(srcPatchW_[col]); + } + + // 縦方向スケール + if (srcPatchH_[row] > 0) { + scaleY = patchHeights_[row] / static_cast(srcPatchH_[row]); + } + + // 平行移動量 + float tx = patchOffsetX_[col] + static_cast(dx) - pivotXf + positionX_; + float ty = patchOffsetY_[row] + static_cast(dy) - pivotYf + positionY_; + + // バイリニア時の伸縮部位置補正 + // if (interpolationMode_ == InterpolationMode::Bilinear) { + // if (col == 1 && srcPatchW_[1] > 1) tx -= scaleX * 0.5f; + // if (row == 1 && srcPatchH_[1] > 1) ty -= scaleY * 0.5f; + // } + + patchScales_[idx] = AffineMatrix(scaleX, 0.0f, 0.0f, scaleY, tx, ty); + patchNeedsAffine_[idx] = true; + } + } + + geometryValid_ = true; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_NINEPATCH_SOURCE_NODE_H +#endif // FLEXIMG_NINEPATCH_SOURCE_NODE_H diff --git a/src/fleximg/nodes/renderer_node.h b/src/fleximg/nodes/renderer_node.h index 8e9c5a3..46d29be 100644 --- a/src/fleximg/nodes/renderer_node.h +++ b/src/fleximg/nodes/renderer_node.h @@ -37,231 +37,267 @@ namespace FLEXIMG_NAMESPACE { class RendererNode : public Node { public: - RendererNode() { - initPorts(1, 1); // 1入力・1出力 - } - - // ======================================== - // 設定API - // ======================================== - - // 仮想スクリーン設定 - // サイズを指定。pivot は setPivot() または setPivotCenter() で別途設定 - void setVirtualScreen(int_fast16_t width, int_fast16_t height) { - virtualWidth_ = static_cast(width); - virtualHeight_ = static_cast(height); - } - - // pivot設定(スクリーン座標でワールド原点の表示位置を指定) - void setPivot(int_fixed x, int_fixed y) { - pivotX_ = x; - pivotY_ = y; - } - void setPivot(float x, float y) { - pivotX_ = float_to_fixed(x); - pivotY_ = float_to_fixed(y); - } - - // 中央をpivotに設定(幾何学的中心) - void setPivotCenter() { - pivotX_ = to_fixed(virtualWidth_) >> 1; - pivotY_ = to_fixed(virtualHeight_) >> 1; - } - - // アクセサ - std::pair getPivot() const { - return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; - } - - // タイル設定 - void setTileConfig(const TileConfig &config) { tileConfig_ = config; } - - void setTileConfig(int_fast16_t tileWidth, int_fast16_t tileHeight) { - tileConfig_ = TileConfig(tileWidth, tileHeight); - } - - // アロケータ設定 - // パイプライン内の各ノードがImageBuffer確保時に使用するアロケータを設定 - // nullptrの場合はデフォルトアロケータを使用 - void setAllocator(core::memory::IAllocator *allocator) { - pipelineAllocator_ = allocator; - } - - // デバッグ用チェッカーボード - void setDebugCheckerboard(bool enabled) { debugCheckerboard_ = enabled; } - - // デバッグ用DataRange可視化 - // 有効時、getDataRangeの範囲外をマゼンタ、AABB差分を青で塗りつぶし - void setDebugDataRange(bool enabled) { debugDataRange_ = enabled; } - - // アクセサ - int virtualWidth() const { return virtualWidth_; } - int virtualHeight() const { return virtualHeight_; } - const TileConfig &tileConfig() const { return tileConfig_; } - - const char *name() const override { return "RendererNode"; } - - // ======================================== - // 実行API - // ======================================== - - // 簡易API(prepare → execute → finalize) - // 戻り値: PrepareStatus(Success = 0、エラー = 非0) - PrepareStatus exec() { - FLEXIMG_METRICS_SCOPE(NodeType::Renderer); - - PrepareStatus result = execPrepare(); - if (result != PrepareStatus::Prepared) { - // エラー時も状態をリセット - execFinalize(); - return result; - } - execProcess(); - execFinalize(); - return PrepareStatus::Prepared; - } + RendererNode() + { + initPorts(1, 1); // 1入力・1出力 + } - // 詳細API - // 戻り値: PrepareStatus(Success = 0、エラー = 非0) - PrepareStatus execPrepare(); - void execProcess(); + // ======================================== + // 設定API + // ======================================== - void execFinalize() { - // 上流へ終了を伝播(プル型) - Node *upstream = upstreamNode(0); - if (upstream) { - upstream->pullFinalize(); + // 仮想スクリーン設定 + // サイズを指定。pivot は setPivot() または setPivotCenter() で別途設定 + void setVirtualScreen(int_fast16_t width, int_fast16_t height) + { + virtualWidth_ = static_cast(width); + virtualHeight_ = static_cast(height); } - // 下流へ終了を伝播(プッシュ型) - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushFinalize(); + // pivot設定(スクリーン座標でワールド原点の表示位置を指定) + void setPivot(int_fixed x, int_fixed y) + { + pivotX_ = x; + pivotY_ = y; + } + void setPivot(float x, float y) + { + pivotX_ = float_to_fixed(x); + pivotY_ = float_to_fixed(y); } - // エントリプールを一括解放 - entryPool_.releaseAll(); - } + // 中央をpivotに設定(幾何学的中心) + void setPivotCenter() + { + pivotX_ = to_fixed(virtualWidth_) >> 1; + pivotY_ = to_fixed(virtualHeight_) >> 1; + } - // パフォーマンス計測結果を取得 - const PerfMetrics &getPerfMetrics() const { return PerfMetrics::instance(); } + // アクセサ + std::pair getPivot() const + { + return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; + } + + // タイル設定 + void setTileConfig(const TileConfig &config) + { + tileConfig_ = config; + } + + void setTileConfig(int_fast16_t tileWidth, int_fast16_t tileHeight) + { + tileConfig_ = TileConfig(tileWidth, tileHeight); + } + + // アロケータ設定 + // パイプライン内の各ノードがImageBuffer確保時に使用するアロケータを設定 + // nullptrの場合はデフォルトアロケータを使用 + void setAllocator(core::memory::IAllocator *allocator) + { + pipelineAllocator_ = allocator; + } + + // デバッグ用チェッカーボード + void setDebugCheckerboard(bool enabled) + { + debugCheckerboard_ = enabled; + } + + // デバッグ用DataRange可視化 + // 有効時、getDataRangeの範囲外をマゼンタ、AABB差分を青で塗りつぶし + void setDebugDataRange(bool enabled) + { + debugDataRange_ = enabled; + } + + // アクセサ + int virtualWidth() const + { + return virtualWidth_; + } + int virtualHeight() const + { + return virtualHeight_; + } + const TileConfig &tileConfig() const + { + return tileConfig_; + } - void resetPerfMetrics() { + const char *name() const override + { + return "RendererNode"; + } + + // ======================================== + // 実行API + // ======================================== + + // 簡易API(prepare → execute → finalize) + // 戻り値: PrepareStatus(Success = 0、エラー = 非0) + PrepareStatus exec() + { + FLEXIMG_METRICS_SCOPE(NodeType::Renderer); + + PrepareStatus result = execPrepare(); + if (result != PrepareStatus::Prepared) { + // エラー時も状態をリセット + execFinalize(); + return result; + } + execProcess(); + execFinalize(); + return PrepareStatus::Prepared; + } + + // 詳細API + // 戻り値: PrepareStatus(Success = 0、エラー = 非0) + PrepareStatus execPrepare(); + void execProcess(); + + void execFinalize() + { + // 上流へ終了を伝播(プル型) + Node *upstream = upstreamNode(0); + if (upstream) { + upstream->pullFinalize(); + } + + // 下流へ終了を伝播(プッシュ型) + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushFinalize(); + } + + // エントリプールを一括解放 + entryPool_.releaseAll(); + } + + // パフォーマンス計測結果を取得 + const PerfMetrics &getPerfMetrics() const + { + return PerfMetrics::instance(); + } + + void resetPerfMetrics() + { #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().reset(); - FormatMetrics::instance().reset(); + PerfMetrics::instance().reset(); + FormatMetrics::instance().reset(); #endif - } + } protected: - // タイル処理(派生クラスでカスタマイズ可能) - // 注: exec()全体の時間はnodes[NodeType::Renderer]に記録される - // 各ノードの合計との差分がオーバーヘッド(タイル管理、データ受け渡し等) - virtual void processTile(int_fast16_t tileX, int_fast16_t tileY) { - RenderRequest request = createTileRequest(tileX, tileY); + // タイル処理(派生クラスでカスタマイズ可能) + // 注: exec()全体の時間はnodes[NodeType::Renderer]に記録される + // 各ノードの合計との差分がオーバーヘッド(タイル管理、データ受け渡し等) + virtual void processTile(int_fast16_t tileX, int_fast16_t tileY) + { + RenderRequest request = createTileRequest(tileX, tileY); + + // 上流からプル + Node *upstream = upstreamNode(0); + if (!upstream) { + context_.resetScanlineResources(); + return; + } - // 上流からプル - Node *upstream = upstreamNode(0); - if (!upstream) { - context_.resetScanlineResources(); - return; + RenderResponse &result = upstream->pullProcess(request); + + // デバッグ: DataRange可視化 + if (debugDataRange_) { + applyDataRangeDebug(upstream, request, result); + } + + // 下流へプッシュ(有効なデータがなくても常に転送) + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(result, request); + } + + // タイル処理完了後にResponseプールをリセット + context_.resetScanlineResources(); } - RenderResponse &result = upstream->pullProcess(request); + // デバッグ用: DataRange可視化処理(resultを直接変更) + void applyDataRangeDebug(Node *upstream, const RenderRequest &request, RenderResponse &result); - // デバッグ: DataRange可視化 - if (debugDataRange_) { - applyDataRangeDebug(upstream, request, result); +private: + int16_t virtualWidth_ = 0; + int16_t virtualHeight_ = 0; + int_fixed pivotX_ = 0; + int_fixed pivotY_ = 0; + TileConfig tileConfig_; + bool debugCheckerboard_ = false; + bool debugDataRange_ = false; + core::memory::IAllocator *pipelineAllocator_ = nullptr; // パイプライン用アロケータ + ImageBufferEntryPool entryPool_; // RenderResponse用エントリプール + RenderContext context_; // レンダリングコンテキスト(allocator + entryPool を統合) + + // タイルサイズ取得 + // 注: パイプライン上のリクエストは必ずスキャンライン(height=1) + // これにより各ノードの最適化が可能になる + int_fast16_t effectiveTileWidth() const + { + return tileConfig_.isEnabled() ? tileConfig_.tileWidth : virtualWidth_; } - // 下流へプッシュ(有効なデータがなくても常に転送) - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushProcess(result, request); + int_fast16_t effectiveTileHeight() const + { + // スキャンライン必須(height=1) + // TileConfig の tileHeight は無視される + return 1; } - // タイル処理完了後にResponseプールをリセット - context_.resetScanlineResources(); - } + // タイル数取得 + int_fast16_t calcTileCountX() const + { + auto tw = effectiveTileWidth(); + return (tw > 0) ? static_cast((virtualWidth_ + tw - 1) / tw) : 1; + } - // デバッグ用: DataRange可視化処理(resultを直接変更) - void applyDataRangeDebug(Node *upstream, const RenderRequest &request, - RenderResponse &result); + int_fast16_t calcTileCountY() const + { + auto th = effectiveTileHeight(); + return (th > 0) ? static_cast((virtualHeight_ + th - 1) / th) : 1; + } -private: - int16_t virtualWidth_ = 0; - int16_t virtualHeight_ = 0; - int_fixed pivotX_ = 0; - int_fixed pivotY_ = 0; - TileConfig tileConfig_; - bool debugCheckerboard_ = false; - bool debugDataRange_ = false; - core::memory::IAllocator *pipelineAllocator_ = - nullptr; // パイプライン用アロケータ - ImageBufferEntryPool entryPool_; // RenderResponse用エントリプール - RenderContext - context_; // レンダリングコンテキスト(allocator + entryPool を統合) - - // タイルサイズ取得 - // 注: パイプライン上のリクエストは必ずスキャンライン(height=1) - // これにより各ノードの最適化が可能になる - int_fast16_t effectiveTileWidth() const { - return tileConfig_.isEnabled() ? tileConfig_.tileWidth : virtualWidth_; - } - - int_fast16_t effectiveTileHeight() const { - // スキャンライン必須(height=1) - // TileConfig の tileHeight は無視される - return 1; - } - - // タイル数取得 - int_fast16_t calcTileCountX() const { - auto tw = effectiveTileWidth(); - return (tw > 0) ? static_cast((virtualWidth_ + tw - 1) / tw) - : 1; - } - - int_fast16_t calcTileCountY() const { - auto th = effectiveTileHeight(); - return (th > 0) ? static_cast((virtualHeight_ + th - 1) / th) - : 1; - } - - // スクリーン全体のRenderRequestを作成 - RenderRequest createScreenRequest() const { - RenderRequest req; - req.width = static_cast(virtualWidth_); - req.height = static_cast(virtualHeight_); - // スクリーン左上(座標0,0)のワールド座標 - req.origin.x = -pivotX_; - req.origin.y = -pivotY_; - return req; - } - - // タイル用のRenderRequestを作成 - RenderRequest createTileRequest(int_fast16_t tileX, - int_fast16_t tileY) const { - auto tw = effectiveTileWidth(); - auto th = effectiveTileHeight(); - auto tileLeft = static_cast(tileX * tw); - auto tileTop = static_cast(tileY * th); - - // タイルサイズ(端の処理) - auto tileW = std::min(tw, virtualWidth_ - tileLeft); - auto tileH = std::min(th, virtualHeight_ - tileTop); - - RenderRequest req; - req.width = static_cast(tileW); - req.height = static_cast(tileH); - // タイル左上のワールド座標 = スクリーン座標 - ワールド原点のスクリーン座標 - req.origin.x = to_fixed(tileLeft) - pivotX_; - req.origin.y = to_fixed(tileTop) - pivotY_; - return req; - } + // スクリーン全体のRenderRequestを作成 + RenderRequest createScreenRequest() const + { + RenderRequest req; + req.width = static_cast(virtualWidth_); + req.height = static_cast(virtualHeight_); + // スクリーン左上(座標0,0)のワールド座標 + req.origin.x = -pivotX_; + req.origin.y = -pivotY_; + return req; + } + + // タイル用のRenderRequestを作成 + RenderRequest createTileRequest(int_fast16_t tileX, int_fast16_t tileY) const + { + auto tw = effectiveTileWidth(); + auto th = effectiveTileHeight(); + auto tileLeft = static_cast(tileX * tw); + auto tileTop = static_cast(tileY * th); + + // タイルサイズ(端の処理) + auto tileW = std::min(tw, virtualWidth_ - tileLeft); + auto tileH = std::min(th, virtualHeight_ - tileTop); + + RenderRequest req; + req.width = static_cast(tileW); + req.height = static_cast(tileH); + // タイル左上のワールド座標 = スクリーン座標 - ワールド原点のスクリーン座標 + req.origin.x = to_fixed(tileLeft) - pivotX_; + req.origin.y = to_fixed(tileTop) - pivotY_; + return req; + } }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -274,90 +310,92 @@ namespace FLEXIMG_NAMESPACE { // RendererNode - 実行API実装 // ============================================================================ -PrepareStatus RendererNode::execPrepare() { +PrepareStatus RendererNode::execPrepare() +{ #ifdef FLEXIMG_DEBUG_PERF_METRICS - // メトリクスをリセット - PerfMetrics::instance().reset(); - FormatMetrics::instance().reset(); + // メトリクスをリセット + PerfMetrics::instance().reset(); + FormatMetrics::instance().reset(); #endif - // アロケータ未設定ならDefaultAllocatorを使用 - if (!pipelineAllocator_) { - pipelineAllocator_ = &core::memory::DefaultAllocator::instance(); - } - - // コンテキストを設定(一括設定でループを1回に削減) - context_.setup(pipelineAllocator_, &entryPool_); - - // ======================================== - // Step 1: 下流へ準備を伝播(AABB取得用) - // ======================================== - Node *downstream = downstreamNode(0); - if (!downstream) { - return PrepareStatus::NoDownstream; - } - - PrepareRequest pushReq; - pushReq.hasPushAffine = false; - pushReq.context = &context_; - - PrepareResponse pushResult = downstream->pushPrepare(pushReq); - if (!pushResult.ok()) { - return pushResult.status; - } - - // ======================================== - // Step 2: virtualScreenサイズを設定 - // ======================================== - // pivot は独立して機能(setPivot() で設定済み、上書きしない) - // virtualScreenサイズは未設定の場合のみ自動設定 - if (virtualWidth_ == 0 || virtualHeight_ == 0) { - virtualWidth_ = pushResult.width; - virtualHeight_ = pushResult.height; - } - - // ======================================== - // Step 3: 上流へ準備を伝播 - // ======================================== - Node *upstream = upstreamNode(0); - if (!upstream) { - return PrepareStatus::NoUpstream; - } - - RenderRequest screenInfo = createScreenRequest(); - PrepareRequest pullReq; - pullReq.width = screenInfo.width; - pullReq.height = screenInfo.height; - pullReq.origin = screenInfo.origin; - pullReq.hasAffine = false; - pullReq.context = &context_; - // 下流が希望するフォーマットを上流に伝播 - pullReq.preferredFormat = pushResult.preferredFormat; - - PrepareResponse pullResult = upstream->pullPrepare(pullReq); - if (!pullResult.ok()) { - return pullResult.status; - } - - // 上流情報は将来の最適化に活用 - (void)pullResult; - - return PrepareStatus::Prepared; -} + // アロケータ未設定ならDefaultAllocatorを使用 + if (!pipelineAllocator_) { + pipelineAllocator_ = &core::memory::DefaultAllocator::instance(); + } -void RendererNode::execProcess() { - auto tileCountX = calcTileCountX(); - auto tileCountY = calcTileCountY(); + // コンテキストを設定(一括設定でループを1回に削減) + context_.setup(pipelineAllocator_, &entryPool_); - for (int_fast16_t ty = 0; ty < tileCountY; ++ty) { - for (int_fast16_t tx = 0; tx < tileCountX; ++tx) { - // デバッグ用チェッカーボード: 市松模様でタイルをスキップ - if (debugCheckerboard_ && ((tx + ty) % 2 == 1)) { - continue; - } - processTile(tx, ty); + // ======================================== + // Step 1: 下流へ準備を伝播(AABB取得用) + // ======================================== + Node *downstream = downstreamNode(0); + if (!downstream) { + return PrepareStatus::NoDownstream; + } + + PrepareRequest pushReq; + pushReq.hasPushAffine = false; + pushReq.context = &context_; + + PrepareResponse pushResult = downstream->pushPrepare(pushReq); + if (!pushResult.ok()) { + return pushResult.status; + } + + // ======================================== + // Step 2: virtualScreenサイズを設定 + // ======================================== + // pivot は独立して機能(setPivot() で設定済み、上書きしない) + // virtualScreenサイズは未設定の場合のみ自動設定 + if (virtualWidth_ == 0 || virtualHeight_ == 0) { + virtualWidth_ = pushResult.width; + virtualHeight_ = pushResult.height; + } + + // ======================================== + // Step 3: 上流へ準備を伝播 + // ======================================== + Node *upstream = upstreamNode(0); + if (!upstream) { + return PrepareStatus::NoUpstream; + } + + RenderRequest screenInfo = createScreenRequest(); + PrepareRequest pullReq; + pullReq.width = screenInfo.width; + pullReq.height = screenInfo.height; + pullReq.origin = screenInfo.origin; + pullReq.hasAffine = false; + pullReq.context = &context_; + // 下流が希望するフォーマットを上流に伝播 + pullReq.preferredFormat = pushResult.preferredFormat; + + PrepareResponse pullResult = upstream->pullPrepare(pullReq); + if (!pullResult.ok()) { + return pullResult.status; + } + + // 上流情報は将来の最適化に活用 + (void)pullResult; + + return PrepareStatus::Prepared; +} + +void RendererNode::execProcess() +{ + auto tileCountX = calcTileCountX(); + auto tileCountY = calcTileCountY(); + + for (int_fast16_t ty = 0; ty < tileCountY; ++ty) { + for (int_fast16_t tx = 0; tx < tileCountX; ++tx) { + // デバッグ用チェッカーボード: 市松模様でタイルをスキップ + if (debugCheckerboard_ && ((tx + ty) % 2 == 1)) { + continue; + } + processTile(tx, ty); + } } - } } // デバッグ用: DataRange可視化処理 @@ -365,139 +403,130 @@ void RendererNode::execProcess() { // - AABBとgetDataRangeの差分: // 青(AABBでは含まれるがgetDataRangeで除外された領域) // - バッファ境界: オレンジ(バッファの開始/終了位置) -void RendererNode::applyDataRangeDebug(Node *upstream, - const RenderRequest &request, - RenderResponse &result) { - // 正確な範囲を取得(スキャンライン単位) - DataRange exactRange = upstream->getDataRange(request); - - // AABBベースの範囲上限を取得 - DataRange aabbRange = upstream->getDataRangeBounds(request); - - // フルサイズのバッファを作成(ゼロ初期化で未定義領域を透明に) - ImageBuffer debugBuffer(request.width, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Zero, pipelineAllocator_); - uint8_t *dst = static_cast(debugBuffer.data()); - - // デバッグ色定義(RGBA) - constexpr uint8_t MAGENTA[] = {255, 0, 255, 255}; // 完全に範囲外 - constexpr uint8_t BLUE[] = {0, 100, 255, - 255}; // AABBでは範囲内だがgetDataRangeで範囲外 - constexpr uint8_t GREEN[] = {0, 255, 100, - 128}; // getDataRange境界マーカー(半透明) - constexpr uint8_t ORANGE[] = {255, 140, 0, - 200}; // バッファ境界マーカー(半透明) - - // まず全体をデバッグ色で初期化 - for (int_fast16_t x = 0; x < request.width; ++x) { - const uint8_t *color; - if (x >= exactRange.startX && x < exactRange.endX) { - // 正確な範囲内: 後で実データで上書き - color = nullptr; - } else if (x >= aabbRange.startX && x < aabbRange.endX) { - // AABBでは範囲内だがgetDataRangeでは範囲外: 青 - color = BLUE; - } else { - // 完全に範囲外: マゼンタ - color = MAGENTA; - } - - if (color) { - uint8_t *p = dst + x * 4; - p[0] = color[0]; - p[1] = color[1]; - p[2] = color[2]; - p[3] = color[3]; - } - } - - // 実データをコピー(単一バッファ・フォーマット変換対応) - if (result.isValid()) { - const ImageBuffer &buf = result.buffer(); - - if (buf.width() > 0) { - // request.originのピクセル位置 - int requestOriginPixelX = from_fixed(request.origin.x); - - // バッファの開始/終了位置(request座標系) - int bufStartX = buf.startX() - requestOriginPixelX; - int bufEndX = buf.endX() - requestOriginPixelX; - - // フォーマット変換の準備 - PixelFormatID srcFormat = buf.formatID(); - FormatConverter converter; - bool needConvert = (srcFormat != PixelFormatIDs::RGBA8_Straight); - if (needConvert) { - converter = resolveConverter(srcFormat, PixelFormatIDs::RGBA8_Straight); - } - - // バッファの内容をコピー - const uint8_t *src = static_cast(buf.data()); - int srcBytesPerPixel = srcFormat->bytesPerPixel; - - for (int i = 0; i < buf.width(); ++i) { - int dstX = bufStartX + i; - if (dstX >= 0 && dstX < request.width) { - uint8_t *p = dst + dstX * 4; - - if (needConvert && converter.func) { - converter.func(p, src + i * srcBytesPerPixel, 1, &converter.ctx); - } else if (!needConvert) { - const uint8_t *s = src + i * 4; - p[0] = s[0]; - p[1] = s[1]; - p[2] = s[2]; - p[3] = s[3]; - } +void RendererNode::applyDataRangeDebug(Node *upstream, const RenderRequest &request, RenderResponse &result) +{ + // 正確な範囲を取得(スキャンライン単位) + DataRange exactRange = upstream->getDataRange(request); + + // AABBベースの範囲上限を取得 + DataRange aabbRange = upstream->getDataRangeBounds(request); + + // フルサイズのバッファを作成(ゼロ初期化で未定義領域を透明に) + ImageBuffer debugBuffer(request.width, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero, pipelineAllocator_); + uint8_t *dst = static_cast(debugBuffer.data()); + + // デバッグ色定義(RGBA) + constexpr uint8_t MAGENTA[] = {255, 0, 255, 255}; // 完全に範囲外 + constexpr uint8_t BLUE[] = {0, 100, 255, 255}; // AABBでは範囲内だがgetDataRangeで範囲外 + constexpr uint8_t GREEN[] = {0, 255, 100, 128}; // getDataRange境界マーカー(半透明) + constexpr uint8_t ORANGE[] = {255, 140, 0, 200}; // バッファ境界マーカー(半透明) + + // まず全体をデバッグ色で初期化 + for (int_fast16_t x = 0; x < request.width; ++x) { + const uint8_t *color; + if (x >= exactRange.startX && x < exactRange.endX) { + // 正確な範囲内: 後で実データで上書き + color = nullptr; + } else if (x >= aabbRange.startX && x < aabbRange.endX) { + // AABBでは範囲内だがgetDataRangeでは範囲外: 青 + color = BLUE; + } else { + // 完全に範囲外: マゼンタ + color = MAGENTA; + } + + if (color) { + uint8_t *p = dst + x * 4; + p[0] = color[0]; + p[1] = color[1]; + p[2] = color[2]; + p[3] = color[3]; } - } + } + + // 実データをコピー(単一バッファ・フォーマット変換対応) + if (result.isValid()) { + const ImageBuffer &buf = result.buffer(); + + if (buf.width() > 0) { + // request.originのピクセル位置 + int requestOriginPixelX = from_fixed(request.origin.x); + + // バッファの開始/終了位置(request座標系) + int bufStartX = buf.startX() - requestOriginPixelX; + int bufEndX = buf.endX() - requestOriginPixelX; + + // フォーマット変換の準備 + PixelFormatID srcFormat = buf.formatID(); + FormatConverter converter; + bool needConvert = (srcFormat != PixelFormatIDs::RGBA8_Straight); + if (needConvert) { + converter = resolveConverter(srcFormat, PixelFormatIDs::RGBA8_Straight); + } + + // バッファの内容をコピー + const uint8_t *src = static_cast(buf.data()); + int srcBytesPerPixel = srcFormat->bytesPerPixel; + + for (int i = 0; i < buf.width(); ++i) { + int dstX = bufStartX + i; + if (dstX >= 0 && dstX < request.width) { + uint8_t *p = dst + dstX * 4; + + if (needConvert && converter.func) { + converter.func(p, src + i * srcBytesPerPixel, 1, &converter.ctx); + } else if (!needConvert) { + const uint8_t *s = src + i * 4; + p[0] = s[0]; + p[1] = s[1]; + p[2] = s[2]; + p[3] = s[3]; + } + } + } + + // バッファ境界マーカーを追加(半透明オレンジ) + auto addBufferBoundary = [&](int x) { + if (x >= 0 && x < request.width) { + uint8_t *p = dst + x * 4; + int alpha = ORANGE[3]; + int invAlpha = 255 - alpha; + p[0] = static_cast((p[0] * invAlpha + ORANGE[0] * alpha) / 255); + p[1] = static_cast((p[1] * invAlpha + ORANGE[1] * alpha) / 255); + p[2] = static_cast((p[2] * invAlpha + ORANGE[2] * alpha) / 255); + p[3] = 255; + } + }; + addBufferBoundary(bufStartX); + if (bufEndX > bufStartX) { + addBufferBoundary(bufEndX - 1); + } + } + } - // バッファ境界マーカーを追加(半透明オレンジ) - auto addBufferBoundary = [&](int x) { + // getDataRange境界マーカーを追加(半透明緑で上書き) + auto addMarker = [&](int16_t x) { if (x >= 0 && x < request.width) { - uint8_t *p = dst + x * 4; - int alpha = ORANGE[3]; - int invAlpha = 255 - alpha; - p[0] = - static_cast((p[0] * invAlpha + ORANGE[0] * alpha) / 255); - p[1] = - static_cast((p[1] * invAlpha + ORANGE[1] * alpha) / 255); - p[2] = - static_cast((p[2] * invAlpha + ORANGE[2] * alpha) / 255); - p[3] = 255; + uint8_t *p = dst + x * 4; + // アルファブレンド(50%) + p[0] = static_cast((p[0] + GREEN[0]) / 2); + p[1] = static_cast((p[1] + GREEN[1]) / 2); + p[2] = static_cast((p[2] + GREEN[2]) / 2); + p[3] = 255; } - }; - addBufferBoundary(bufStartX); - if (bufEndX > bufStartX) { - addBufferBoundary(bufEndX - 1); - } - } - } - - // getDataRange境界マーカーを追加(半透明緑で上書き) - auto addMarker = [&](int16_t x) { - if (x >= 0 && x < request.width) { - uint8_t *p = dst + x * 4; - // アルファブレンド(50%) - p[0] = static_cast((p[0] + GREEN[0]) / 2); - p[1] = static_cast((p[1] + GREEN[1]) / 2); - p[2] = static_cast((p[2] + GREEN[2]) / 2); - p[3] = 255; - } - }; - addMarker(exactRange.startX); - if (exactRange.endX > 0) - addMarker(static_cast(exactRange.endX - 1)); - - // resultをクリアして新しいデバッグバッファを設定 - result.clear(); - debugBuffer.setOrigin(request.origin); - result.addBuffer(std::move(debugBuffer)); - result.origin = request.origin; + }; + addMarker(exactRange.startX); + if (exactRange.endX > 0) addMarker(static_cast(exactRange.endX - 1)); + + // resultをクリアして新しいデバッグバッファを設定 + result.clear(); + debugBuffer.setOrigin(request.origin); + result.addBuffer(std::move(debugBuffer)); + result.origin = request.origin; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_RENDERER_NODE_H +#endif // FLEXIMG_RENDERER_NODE_H diff --git a/src/fleximg/nodes/sink_node.h b/src/fleximg/nodes/sink_node.h index 49b84b5..85fc7d7 100644 --- a/src/fleximg/nodes/sink_node.h +++ b/src/fleximg/nodes/sink_node.h @@ -31,87 +31,118 @@ namespace FLEXIMG_NAMESPACE { class SinkNode : public Node, public AffineCapability { public: - // コンストラクタ - SinkNode() { - initPorts(1, 0); // 入力1、出力0 - } - - SinkNode(const ViewPort &vp, int_fixed pivotX = 0, int_fixed pivotY = 0) - : target_(vp), pivotX_(pivotX), pivotY_(pivotY) { - initPorts(1, 0); - } - - // ターゲット設定 - void setTarget(const ViewPort &vp) { target_ = vp; } - - // pivot 設定(出力バッファ座標、変換の中心点) - void setPivot(int_fixed x, int_fixed y) { - pivotX_ = x; - pivotY_ = y; - } - void setPivot(float x, float y) { - pivotX_ = float_to_fixed(x); - pivotY_ = float_to_fixed(y); - } - - // 便利メソッド: ターゲット中央を pivot に設定 - void setPivotCenter() { - pivotX_ = to_fixed(target_.width / 2); - pivotY_ = to_fixed(target_.height / 2); - } - - // アクセサ - const ViewPort &target() const { return target_; } - ViewPort &target() { return target_; } - int_fixed pivotX() const { return pivotX_; } - int_fixed pivotY() const { return pivotY_; } - std::pair getPivot() const { - return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; - } - - // キャンバスサイズ(targetから取得) - int16_t canvasWidth() const { return target_.width; } - int16_t canvasHeight() const { return target_.height; } - - const char *name() const override { return "SinkNode"; } + // コンストラクタ + SinkNode() + { + initPorts(1, 0); // 入力1、出力0 + } + + SinkNode(const ViewPort &vp, int_fixed pivotX = 0, int_fixed pivotY = 0) + : target_(vp), pivotX_(pivotX), pivotY_(pivotY) + { + initPorts(1, 0); + } + + // ターゲット設定 + void setTarget(const ViewPort &vp) + { + target_ = vp; + } + + // pivot 設定(出力バッファ座標、変換の中心点) + void setPivot(int_fixed x, int_fixed y) + { + pivotX_ = x; + pivotY_ = y; + } + void setPivot(float x, float y) + { + pivotX_ = float_to_fixed(x); + pivotY_ = float_to_fixed(y); + } + + // 便利メソッド: ターゲット中央を pivot に設定 + void setPivotCenter() + { + pivotX_ = to_fixed(target_.width / 2); + pivotY_ = to_fixed(target_.height / 2); + } + + // アクセサ + const ViewPort &target() const + { + return target_; + } + ViewPort &target() + { + return target_; + } + int_fixed pivotX() const + { + return pivotX_; + } + int_fixed pivotY() const + { + return pivotY_; + } + std::pair getPivot() const + { + return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; + } + + // キャンバスサイズ(targetから取得) + int16_t canvasWidth() const + { + return target_.width; + } + int16_t canvasHeight() const + { + return target_.height; + } + + const char *name() const override + { + return "SinkNode"; + } protected: - int nodeTypeForMetrics() const override { return NodeType::Sink; } + int nodeTypeForMetrics() const override + { + return NodeType::Sink; + } protected: - // ======================================== - // Template Method フック - // ======================================== + // ======================================== + // Template Method フック + // ======================================== - // onPushPrepare: アフィン情報を受け取り、事前計算を行う - // SinkNodeは終端なので下流への伝播なし、PrepareResponseを返す - PrepareResponse onPushPrepare(const PrepareRequest &request) override; + // onPushPrepare: アフィン情報を受け取り、事前計算を行う + // SinkNodeは終端なので下流への伝播なし、PrepareResponseを返す + PrepareResponse onPushPrepare(const PrepareRequest &request) override; - // onPushProcess: タイル単位で呼び出され、出力バッファに書き込み - // SinkNodeは終端なので下流への伝播なし - void onPushProcess(RenderResponse &input, - const RenderRequest &request) override; + // onPushProcess: タイル単位で呼び出され、出力バッファに書き込み + // SinkNodeは終端なので下流への伝播なし + void onPushProcess(RenderResponse &input, const RenderRequest &request) override; private: - ViewPort target_; - int_fixed pivotX_ = 0; // 変換の中心点X(出力バッファ座標、固定小数点 Q16.16) - int_fixed pivotY_ = 0; // 変換の中心点Y(出力バッファ座標、固定小数点 Q16.16) - - // アフィン伝播用メンバ変数(事前計算済み) - Matrix2x2_fixed invMatrix_; // 逆行列(固定小数点) - int_fixed baseTx_ = 0; // 事前計算済みオフセットX(Q16.16、pivot込み) - int_fixed baseTy_ = 0; // 事前計算済みオフセットY(Q16.16、pivot込み) - bool hasAffine_ = false; // アフィン変換が伝播されているか - - // アフィン変換付きプッシュ処理 - void pushProcessWithAffine(RenderResponse &input); - - // アフィン変換実装(事前計算済み値を使用) - void applyAffine(ViewPort &dst, const ViewPort &src, int_fixed srcOriginX, - int_fixed srcOriginY); + ViewPort target_; + int_fixed pivotX_ = 0; // 変換の中心点X(出力バッファ座標、固定小数点 Q16.16) + int_fixed pivotY_ = 0; // 変換の中心点Y(出力バッファ座標、固定小数点 Q16.16) + + // アフィン伝播用メンバ変数(事前計算済み) + Matrix2x2_fixed invMatrix_; // 逆行列(固定小数点) + int_fixed baseTx_ = 0; // 事前計算済みオフセットX(Q16.16、pivot込み) + int_fixed baseTy_ = 0; // 事前計算済みオフセットY(Q16.16、pivot込み) + bool hasAffine_ = false; // アフィン変換が伝播されているか + + // アフィン変換付きプッシュ処理 + void pushProcessWithAffine(RenderResponse &input); + + // アフィン変換実装(事前計算済み値を使用) + void applyAffine(ViewPort &dst, const ViewPort &src, int_fixed srcOriginX, int_fixed srcOriginY); }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -124,197 +155,184 @@ namespace FLEXIMG_NAMESPACE { // SinkNode - Template Method フック実装 // ============================================================================ -PrepareResponse SinkNode::onPushPrepare(const PrepareRequest &request) { - // アフィン情報を受け取り、事前計算を行う - // localMatrix_ も含めて合成 - AffineMatrix combinedMatrix; - bool hasTransform = false; - - if (request.hasPushAffine || hasLocalTransform()) { - // 行列合成: localMatrix_ * request.pushAffineMatrix - // Pull側と同じ合成順序(自身の変換を先に掛ける) - if (request.hasPushAffine) { - combinedMatrix = localMatrix_ * request.pushAffineMatrix; +PrepareResponse SinkNode::onPushPrepare(const PrepareRequest &request) +{ + // アフィン情報を受け取り、事前計算を行う + // localMatrix_ も含めて合成 + AffineMatrix combinedMatrix; + bool hasTransform = false; + + if (request.hasPushAffine || hasLocalTransform()) { + // 行列合成: localMatrix_ * request.pushAffineMatrix + // Pull側と同じ合成順序(自身の変換を先に掛ける) + if (request.hasPushAffine) { + combinedMatrix = localMatrix_ * request.pushAffineMatrix; + } else { + combinedMatrix = localMatrix_; + } + hasTransform = true; + + // 逆行列を固定小数点に変換 + invMatrix_ = inverseFixed(combinedMatrix); + + if (invMatrix_.valid) { + // 変換式: src = Inv * (buf - pivot - tx) + // - pivot: ワールド原点 (0,0) に対応するバッファ座標 + 回転中心 + // - tx/ty: 平行移動(回転の影響を受けない) + // 全て int_fixed (Q16.16) で演算し、小数精度を保持 + int_fixed txFixed = float_to_fixed(combinedMatrix.tx); + int_fixed tyFixed = float_to_fixed(combinedMatrix.ty); + // (pivot + tx) を Inv で変換 + int64_t combinedX = pivotX_ + txFixed; + int64_t combinedY = pivotY_ + tyFixed; + int64_t invCombinedX = (combinedX * invMatrix_.a + combinedY * invMatrix_.b) >> INT_FIXED_SHIFT; + int64_t invCombinedY = (combinedX * invMatrix_.c + combinedY * invMatrix_.d) >> INT_FIXED_SHIFT; + // baseTx_ = -Inv * (pivot + tx) + baseTx_ = -static_cast(invCombinedX); + baseTy_ = -static_cast(invCombinedY); + } + hasAffine_ = true; } else { - combinedMatrix = localMatrix_; + hasAffine_ = false; } - hasTransform = true; - - // 逆行列を固定小数点に変換 - invMatrix_ = inverseFixed(combinedMatrix); - - if (invMatrix_.valid) { - // 変換式: src = Inv * (buf - pivot - tx) - // - pivot: ワールド原点 (0,0) に対応するバッファ座標 + 回転中心 - // - tx/ty: 平行移動(回転の影響を受けない) - // 全て int_fixed (Q16.16) で演算し、小数精度を保持 - int_fixed txFixed = float_to_fixed(combinedMatrix.tx); - int_fixed tyFixed = float_to_fixed(combinedMatrix.ty); - // (pivot + tx) を Inv で変換 - int64_t combinedX = pivotX_ + txFixed; - int64_t combinedY = pivotY_ + tyFixed; - int64_t invCombinedX = - (combinedX * invMatrix_.a + combinedY * invMatrix_.b) >> - INT_FIXED_SHIFT; - int64_t invCombinedY = - (combinedX * invMatrix_.c + combinedY * invMatrix_.d) >> - INT_FIXED_SHIFT; - // baseTx_ = -Inv * (pivot + tx) - baseTx_ = -static_cast(invCombinedX); - baseTy_ = -static_cast(invCombinedY); + + // SinkNodeは終端なので下流への伝播なし + // プッシュアフィン変換がある場合、入力側で必要なAABBを計算 + PrepareResponse result; + result.status = PrepareStatus::Prepared; + result.preferredFormat = target_.formatID; + + if (hasTransform && invMatrix_.valid) { + // 逆行列でAABBを計算(buf→src変換の結果範囲) + // 変換式: src = Inv * (buf - pivot - tx) + // (pivot + tx) を "pivot" として渡す + AffineMatrix aabbMatrix = combinedMatrix; + aabbMatrix.tx = 0; + aabbMatrix.ty = 0; + int_fixed combinedPivotX = pivotX_ + float_to_fixed(combinedMatrix.tx); + int_fixed combinedPivotY = pivotY_ + float_to_fixed(combinedMatrix.ty); + calcInverseAffineAABB(target_.width, target_.height, {combinedPivotX, combinedPivotY}, aabbMatrix, result.width, + result.height, result.origin); + } else { + // アフィンなしの場合 + // 変換式: src = buf - tx - pivot + // バッファ左上 (0, 0) のワールド座標 = -tx - pivot + result.width = target_.width; + result.height = target_.height; + // pivotX_/pivotY_ は int_fixed (Q16.16) で小数成分を保持 + result.origin = {-float_to_fixed(localMatrix_.tx) - pivotX_, -float_to_fixed(localMatrix_.ty) - pivotY_}; } - hasAffine_ = true; - } else { - hasAffine_ = false; - } - - // SinkNodeは終端なので下流への伝播なし - // プッシュアフィン変換がある場合、入力側で必要なAABBを計算 - PrepareResponse result; - result.status = PrepareStatus::Prepared; - result.preferredFormat = target_.formatID; - - if (hasTransform && invMatrix_.valid) { - // 逆行列でAABBを計算(buf→src変換の結果範囲) - // 変換式: src = Inv * (buf - pivot - tx) - // (pivot + tx) を "pivot" として渡す - AffineMatrix aabbMatrix = combinedMatrix; - aabbMatrix.tx = 0; - aabbMatrix.ty = 0; - int_fixed combinedPivotX = pivotX_ + float_to_fixed(combinedMatrix.tx); - int_fixed combinedPivotY = pivotY_ + float_to_fixed(combinedMatrix.ty); - calcInverseAffineAABB(target_.width, target_.height, - {combinedPivotX, combinedPivotY}, aabbMatrix, - result.width, result.height, result.origin); - } else { - // アフィンなしの場合 - // 変換式: src = buf - tx - pivot - // バッファ左上 (0, 0) のワールド座標 = -tx - pivot - result.width = target_.width; - result.height = target_.height; - // pivotX_/pivotY_ は int_fixed (Q16.16) で小数成分を保持 - result.origin = {-float_to_fixed(localMatrix_.tx) - pivotX_, - -float_to_fixed(localMatrix_.ty) - pivotY_}; - } - return result; + return result; } -void SinkNode::onPushProcess(RenderResponse &input, - const RenderRequest &request) { - (void)request; // 現在は未使用 - - if (!input.isValid() || !target_.isValid()) - return; - - FLEXIMG_METRICS_SCOPE(NodeType::Sink); - - // フォーマット変換を実行 - consolidateIfNeeded(input, target_.formatID); - - // アフィン変換が伝播されている場合はDDA処理 - if (hasAffine_) { - pushProcessWithAffine(input); - return; - } - - // 配置計算(固定小数点演算) - // 変換式: src = buf - tx - pivot → buf = src + tx + pivot - // 全て int_fixed (Q16.16) で演算し、最終的にピクセル座標へ変換 - ViewPort inputView = input.view(); - int_fixed txFixed = float_to_fixed(localMatrix_.tx); - int_fixed tyFixed = float_to_fixed(localMatrix_.ty); - auto dstX = - static_cast(from_fixed(input.origin.x + txFixed + pivotX_)); - auto dstY = - static_cast(from_fixed(input.origin.y + tyFixed + pivotY_)); - - // クリッピング処理 - int_fast16_t srcX = 0, srcY = 0; - if (dstX < 0) { - srcX = -dstX; - dstX = 0; - } - if (dstY < 0) { - srcY = -dstY; - dstY = 0; - } - - int_fast32_t copyW = - std::min(inputView.width - srcX, target_.width - dstX); - int_fast32_t copyH = - std::min(inputView.height - srcY, target_.height - dstY); - - if (copyW <= 0 || copyH <= 0) - return; - - // FormatConverter でターゲットに直接変換書き込み - // (同一フォーマットは memcpy、異なる場合は解決済み変換関数で処理) - auto converter = resolveConverter(inputView.formatID, target_.formatID, - &input.buffer().auxInfo()); - - if (converter) { - for (int_fast32_t y = 0; y < copyH; ++y) { - const void *srcRow = inputView.pixelAt(srcX, srcY + static_cast(y)); - void *dstRow = target_.pixelAt(dstX, dstY + static_cast(y)); - converter(dstRow, srcRow, static_cast(copyW)); +void SinkNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + (void)request; // 現在は未使用 + + if (!input.isValid() || !target_.isValid()) return; + + FLEXIMG_METRICS_SCOPE(NodeType::Sink); + + // フォーマット変換を実行 + consolidateIfNeeded(input, target_.formatID); + + // アフィン変換が伝播されている場合はDDA処理 + if (hasAffine_) { + pushProcessWithAffine(input); + return; + } + + // 配置計算(固定小数点演算) + // 変換式: src = buf - tx - pivot → buf = src + tx + pivot + // 全て int_fixed (Q16.16) で演算し、最終的にピクセル座標へ変換 + ViewPort inputView = input.view(); + int_fixed txFixed = float_to_fixed(localMatrix_.tx); + int_fixed tyFixed = float_to_fixed(localMatrix_.ty); + auto dstX = static_cast(from_fixed(input.origin.x + txFixed + pivotX_)); + auto dstY = static_cast(from_fixed(input.origin.y + tyFixed + pivotY_)); + + // クリッピング処理 + int_fast16_t srcX = 0, srcY = 0; + if (dstX < 0) { + srcX = -dstX; + dstX = 0; + } + if (dstY < 0) { + srcY = -dstY; + dstY = 0; + } + + int_fast32_t copyW = std::min(inputView.width - srcX, target_.width - dstX); + int_fast32_t copyH = std::min(inputView.height - srcY, target_.height - dstY); + + if (copyW <= 0 || copyH <= 0) return; + + // FormatConverter でターゲットに直接変換書き込み + // (同一フォーマットは memcpy、異なる場合は解決済み変換関数で処理) + auto converter = resolveConverter(inputView.formatID, target_.formatID, &input.buffer().auxInfo()); + + if (converter) { + for (int_fast32_t y = 0; y < copyH; ++y) { + const void *srcRow = inputView.pixelAt(srcX, srcY + static_cast(y)); + void *dstRow = target_.pixelAt(dstX, dstY + static_cast(y)); + converter(dstRow, srcRow, static_cast(copyW)); + } } - } } // ============================================================================ // SinkNode - private ヘルパーメソッド実装 // ============================================================================ -void SinkNode::pushProcessWithAffine(RenderResponse &input) { - // 特異行列チェック - if (!invMatrix_.valid) { - return; - } - - // ターゲットフォーマットに変換(フォーマットが異なる場合のみ) - PixelFormatID targetFormat = target_.formatID; - ImageBuffer convertedBuffer; - ViewPort inputView; - - if (input.buffer().formatID() != targetFormat) { - convertedBuffer = ImageBuffer(input.buffer()).toFormat(targetFormat); - inputView = convertedBuffer.view(); - } else { - inputView = input.view(); - } - - // アフィン変換を適用してターゲットに書き込み(pivot は事前計算済み) - applyAffine(target_, inputView, input.origin.x, input.origin.y); +void SinkNode::pushProcessWithAffine(RenderResponse &input) +{ + // 特異行列チェック + if (!invMatrix_.valid) { + return; + } + + // ターゲットフォーマットに変換(フォーマットが異なる場合のみ) + PixelFormatID targetFormat = target_.formatID; + ImageBuffer convertedBuffer; + ViewPort inputView; + + if (input.buffer().formatID() != targetFormat) { + convertedBuffer = ImageBuffer(input.buffer()).toFormat(targetFormat); + inputView = convertedBuffer.view(); + } else { + inputView = input.view(); + } + + // アフィン変換を適用してターゲットに書き込み(pivot は事前計算済み) + applyAffine(target_, inputView, input.origin.x, input.origin.y); } -void SinkNode::applyAffine(ViewPort &dst, const ViewPort &src, - int_fixed srcOriginX, int_fixed srcOriginY) { - if (!invMatrix_.valid) - return; - - // srcOrigin分のみ計算(baseTx_/baseTy_ は pivot 込みで事前計算済み) - // srcOrigin は入力バッファ左上のワールド座標 - const int32_t srcOriginXInt = from_fixed(srcOriginX); - const int32_t srcOriginYInt = from_fixed(srcOriginY); - - // baseTx_はすでにworldオフセットを含む - // srcOriginは入力バッファの左上のworld座標なので減算 - const int_fixed fixedTx = baseTx_ - (srcOriginXInt << INT_FIXED_SHIFT); - const int_fixed fixedTy = baseTy_ - (srcOriginYInt << INT_FIXED_SHIFT); - - // ピクセル中心オフセット(逆行列用) - int_fixed rowOffsetX = invMatrix_.b >> 1; - int_fixed rowOffsetY = invMatrix_.d >> 1; - int_fixed dxOffsetX = invMatrix_.a >> 1; - int_fixed dxOffsetY = invMatrix_.c >> 1; - - // 共通DDA処理を呼び出し - view_ops::affineTransform(dst, src, fixedTx, fixedTy, invMatrix_, rowOffsetX, - rowOffsetY, dxOffsetX, dxOffsetY); +void SinkNode::applyAffine(ViewPort &dst, const ViewPort &src, int_fixed srcOriginX, int_fixed srcOriginY) +{ + if (!invMatrix_.valid) return; + + // srcOrigin分のみ計算(baseTx_/baseTy_ は pivot 込みで事前計算済み) + // srcOrigin は入力バッファ左上のワールド座標 + const int32_t srcOriginXInt = from_fixed(srcOriginX); + const int32_t srcOriginYInt = from_fixed(srcOriginY); + + // baseTx_はすでにworldオフセットを含む + // srcOriginは入力バッファの左上のworld座標なので減算 + const int_fixed fixedTx = baseTx_ - (srcOriginXInt << INT_FIXED_SHIFT); + const int_fixed fixedTy = baseTy_ - (srcOriginYInt << INT_FIXED_SHIFT); + + // ピクセル中心オフセット(逆行列用) + int_fixed rowOffsetX = invMatrix_.b >> 1; + int_fixed rowOffsetY = invMatrix_.d >> 1; + int_fixed dxOffsetX = invMatrix_.a >> 1; + int_fixed dxOffsetY = invMatrix_.c >> 1; + + // 共通DDA処理を呼び出し + view_ops::affineTransform(dst, src, fixedTx, fixedTy, invMatrix_, rowOffsetX, rowOffsetY, dxOffsetX, dxOffsetY); } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_SINK_NODE_H +#endif // FLEXIMG_SINK_NODE_H diff --git a/src/fleximg/nodes/source_node.h b/src/fleximg/nodes/source_node.h index c16dff0..7061431 100644 --- a/src/fleximg/nodes/source_node.h +++ b/src/fleximg/nodes/source_node.h @@ -19,8 +19,8 @@ namespace FLEXIMG_NAMESPACE { // ======================================================================== enum class InterpolationMode { - Nearest, // 最近傍補間(デフォルト) - Bilinear // バイリニア補間(RGBA8888のみ対応) + Nearest, // 最近傍補間(デフォルト) + Bilinear // バイリニア補間(RGBA8888のみ対応) }; // ======================================================================== @@ -41,142 +41,172 @@ enum class InterpolationMode { class SourceNode : public Node, public AffineCapability { public: - // コンストラクタ - SourceNode() { - initPorts(0, 1); // 入力0、出力1 - } - - SourceNode(const ViewPort &vp, int_fixed pivotX = 0, int_fixed pivotY = 0) - : source_(vp), pivotX_(pivotX), pivotY_(pivotY) { - initPorts(0, 1); - } - - // ソース設定 - void setSource(const ViewPort &vp) { - source_ = vp; - palette_ = PaletteData(); - } - void setSource(const ViewPort &vp, const PaletteData &palette) { - source_ = vp; - palette_ = palette; - } - - // 基準点設定(pivot: 画像内のアンカーポイント) - void setPivot(int_fixed x, int_fixed y) { - pivotX_ = x; - pivotY_ = y; - } - void setPivot(float x, float y) { - pivotX_ = float_to_fixed(x); - pivotY_ = float_to_fixed(y); - } - - // アクセサ - const ViewPort &source() const { return source_; } - int_fixed pivotX() const { return pivotX_; } - int_fixed pivotY() const { return pivotY_; } - std::pair getPivot() const { - return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; - } - - // ユーザー向けAPI: position(setTranslation のエイリアス、後方互換) - void setPosition(float x, float y) { setTranslation(x, y); } - std::pair getPosition() const { - return {localMatrix_.tx, localMatrix_.ty}; - } - - // カラーキー設定(アルファなしフォーマットで特定色を透明化) - void setColorKey(uint32_t colorKeyRGBA8, uint32_t replaceRGBA8 = 0) { - colorKeyRGBA8_ = colorKeyRGBA8; - colorKeyReplace_ = replaceRGBA8; - } - void clearColorKey() { - colorKeyRGBA8_ = 0; - colorKeyReplace_ = 0; - } - - // 補間モード設定 - void setInterpolationMode(InterpolationMode mode) { - interpolationMode_ = mode; - } - InterpolationMode interpolationMode() const { return interpolationMode_; } - - // エッジフェードアウト設定(バイリニア補間時のみ有効) - // フェード有効な辺では出力範囲が0.5ピクセル拡張され、境界がなめらかに透明化 - // フェード無効な辺では出力範囲はNearestと同じ、境界ピクセルはクランプ - void setEdgeFade(uint8_t flags) { edgeFadeFlags_ = flags; } - uint8_t edgeFade() const { return edgeFadeFlags_; } - - const char *name() const override { return "SourceNode"; } - - // ======================================== - // Template Method フック - // ======================================== - - // onPullPrepare: アフィン情報を受け取り、事前計算を行う - // SourceNodeは終端なので上流への伝播なし、PrepareResponseを返す - PrepareResponse onPullPrepare(const PrepareRequest &request) override; - - // onPullProcess: ソース画像のスキャンラインを返す - // SourceNodeは入力がないため、上流を呼び出さずに直接処理 - RenderResponse &onPullProcess(const RenderRequest &request) override; - - // getDataRange: スキャンライン単位の正確なデータ範囲を返す - // アフィン変換がある場合、calcScanlineRangeで厳密な有効範囲を計算 - // AABB上限が必要な場合は getDataRangeBounds() を使用 - DataRange getDataRange(const RenderRequest &request) const override; + // コンストラクタ + SourceNode() + { + initPorts(0, 1); // 入力0、出力1 + } + + SourceNode(const ViewPort &vp, int_fixed pivotX = 0, int_fixed pivotY = 0) + : source_(vp), pivotX_(pivotX), pivotY_(pivotY) + { + initPorts(0, 1); + } + + // ソース設定 + void setSource(const ViewPort &vp) + { + source_ = vp; + palette_ = PaletteData(); + } + void setSource(const ViewPort &vp, const PaletteData &palette) + { + source_ = vp; + palette_ = palette; + } + + // 基準点設定(pivot: 画像内のアンカーポイント) + void setPivot(int_fixed x, int_fixed y) + { + pivotX_ = x; + pivotY_ = y; + } + void setPivot(float x, float y) + { + pivotX_ = float_to_fixed(x); + pivotY_ = float_to_fixed(y); + } + + // アクセサ + const ViewPort &source() const + { + return source_; + } + int_fixed pivotX() const + { + return pivotX_; + } + int_fixed pivotY() const + { + return pivotY_; + } + std::pair getPivot() const + { + return {fixed_to_float(pivotX_), fixed_to_float(pivotY_)}; + } + + // ユーザー向けAPI: position(setTranslation のエイリアス、後方互換) + void setPosition(float x, float y) + { + setTranslation(x, y); + } + std::pair getPosition() const + { + return {localMatrix_.tx, localMatrix_.ty}; + } + + // カラーキー設定(アルファなしフォーマットで特定色を透明化) + void setColorKey(uint32_t colorKeyRGBA8, uint32_t replaceRGBA8 = 0) + { + colorKeyRGBA8_ = colorKeyRGBA8; + colorKeyReplace_ = replaceRGBA8; + } + void clearColorKey() + { + colorKeyRGBA8_ = 0; + colorKeyReplace_ = 0; + } + + // 補間モード設定 + void setInterpolationMode(InterpolationMode mode) + { + interpolationMode_ = mode; + } + InterpolationMode interpolationMode() const + { + return interpolationMode_; + } + + // エッジフェードアウト設定(バイリニア補間時のみ有効) + // フェード有効な辺では出力範囲が0.5ピクセル拡張され、境界がなめらかに透明化 + // フェード無効な辺では出力範囲はNearestと同じ、境界ピクセルはクランプ + void setEdgeFade(uint8_t flags) + { + edgeFadeFlags_ = flags; + } + uint8_t edgeFade() const + { + return edgeFadeFlags_; + } + + const char *name() const override + { + return "SourceNode"; + } + + // ======================================== + // Template Method フック + // ======================================== + + // onPullPrepare: アフィン情報を受け取り、事前計算を行う + // SourceNodeは終端なので上流への伝播なし、PrepareResponseを返す + PrepareResponse onPullPrepare(const PrepareRequest &request) override; + + // onPullProcess: ソース画像のスキャンラインを返す + // SourceNodeは入力がないため、上流を呼び出さずに直接処理 + RenderResponse &onPullProcess(const RenderRequest &request) override; + + // getDataRange: スキャンライン単位の正確なデータ範囲を返す + // アフィン変換がある場合、calcScanlineRangeで厳密な有効範囲を計算 + // AABB上限が必要な場合は getDataRangeBounds() を使用 + DataRange getDataRange(const RenderRequest &request) const override; private: - ViewPort source_; - PaletteData palette_; // パレット情報(インデックスフォーマット用、非所有) - int_fixed pivotX_ = - 0; // 画像内の基準点X(pivot: 回転・配置の中心、固定小数点 Q16.16) - int_fixed pivotY_ = - 0; // 画像内の基準点Y(pivot: 回転・配置の中心、固定小数点 Q16.16) - // 注: 配置位置は localMatrix_.tx/ty で管理(AffineCapability から継承) - InterpolationMode interpolationMode_ = InterpolationMode::Nearest; - uint8_t edgeFadeFlags_ = EdgeFade_All; // デフォルト: 全辺フェードアウト有効 - uint32_t colorKeyRGBA8_ = 0; // カラーキー比較値(RGBA8、alpha込み) - uint32_t colorKeyReplace_ = 0; // カラーキー差し替え値(通常は透明黒0) - - // アフィン伝播用メンバ変数(事前計算済み) - AffinePrecomputed affine_; // 逆行列・ピクセル中心オフセット - bool hasAffine_ = false; // アフィン変換が伝播されているか - bool useBilinear_ = false; // バイリニア補間を使用するか(事前計算結果) - - // フォーマット交渉(下流からの希望フォーマット) - PixelFormatID preferredFormat_ = PixelFormatIDs::RGBA8_Straight; - - // LovyanGFX方式の範囲計算用事前計算値 - int_fixed xs1_ = 0, xs2_ = 0; // X方向の範囲境界(invAに依存) - int_fixed ys1_ = 0, ys2_ = 0; // Y方向の範囲境界(invCに依存) - int_fixed fpWidth_ = 0; // ソース幅(Q16.16固定小数点) - int_fixed fpHeight_ = 0; // ソース高さ(Q16.16固定小数点) - int_fixed baseTxWithOffsets_ = - 0; // 事前計算統合: invTx + srcPivot + rowOffset + dxOffset - int_fixed baseTyWithOffsets_ = - 0; // 事前計算統合: invTy + srcPivot + rowOffset + dxOffset - - // Prepare時のorigin(Process時の差分計算用) - int_fixed prepareOriginX_ = 0; - int_fixed prepareOriginY_ = 0; - - // getDataRangeキャッシュ(同一スキャンラインでの重複計算を回避) - // NinePatchSourceNode等から同一requestで複数回呼ばれるケースに対応 - mutable core::DataRangeCache dataRangeCache_; - - // スキャンライン有効範囲を計算(pullProcessWithAffineで使用) - // 戻り値: true=有効範囲あり, false=有効範囲なし - // baseXWithHalf/baseYWithHalf はオプショナル出力(nullptrなら出力しない) - bool calcScanlineRange(const RenderRequest &request, int32_t &dxStart, - int32_t &dxEnd, int32_t *baseXWithHalf = nullptr, - int32_t *baseYWithHalf = nullptr) const; - - // アフィン変換付きプル処理(スキャンライン専用) - RenderResponse &pullProcessWithAffine(const RenderRequest &request); + ViewPort source_; + PaletteData palette_; // パレット情報(インデックスフォーマット用、非所有) + int_fixed pivotX_ = 0; // 画像内の基準点X(pivot: 回転・配置の中心、固定小数点 Q16.16) + int_fixed pivotY_ = 0; // 画像内の基準点Y(pivot: 回転・配置の中心、固定小数点 Q16.16) + // 注: 配置位置は localMatrix_.tx/ty で管理(AffineCapability から継承) + InterpolationMode interpolationMode_ = InterpolationMode::Nearest; + uint8_t edgeFadeFlags_ = EdgeFade_All; // デフォルト: 全辺フェードアウト有効 + uint32_t colorKeyRGBA8_ = 0; // カラーキー比較値(RGBA8、alpha込み) + uint32_t colorKeyReplace_ = 0; // カラーキー差し替え値(通常は透明黒0) + + // アフィン伝播用メンバ変数(事前計算済み) + AffinePrecomputed affine_; // 逆行列・ピクセル中心オフセット + bool hasAffine_ = false; // アフィン変換が伝播されているか + bool useBilinear_ = false; // バイリニア補間を使用するか(事前計算結果) + + // フォーマット交渉(下流からの希望フォーマット) + PixelFormatID preferredFormat_ = PixelFormatIDs::RGBA8_Straight; + + // LovyanGFX方式の範囲計算用事前計算値 + int_fixed xs1_ = 0, xs2_ = 0; // X方向の範囲境界(invAに依存) + int_fixed ys1_ = 0, ys2_ = 0; // Y方向の範囲境界(invCに依存) + int_fixed fpWidth_ = 0; // ソース幅(Q16.16固定小数点) + int_fixed fpHeight_ = 0; // ソース高さ(Q16.16固定小数点) + int_fixed baseTxWithOffsets_ = 0; // 事前計算統合: invTx + srcPivot + rowOffset + dxOffset + int_fixed baseTyWithOffsets_ = 0; // 事前計算統合: invTy + srcPivot + rowOffset + dxOffset + + // Prepare時のorigin(Process時の差分計算用) + int_fixed prepareOriginX_ = 0; + int_fixed prepareOriginY_ = 0; + + // getDataRangeキャッシュ(同一スキャンラインでの重複計算を回避) + // NinePatchSourceNode等から同一requestで複数回呼ばれるケースに対応 + mutable core::DataRangeCache dataRangeCache_; + + // スキャンライン有効範囲を計算(pullProcessWithAffineで使用) + // 戻り値: true=有効範囲あり, false=有効範囲なし + // baseXWithHalf/baseYWithHalf はオプショナル出力(nullptrなら出力しない) + bool calcScanlineRange(const RenderRequest &request, int32_t &dxStart, int32_t &dxEnd, + int32_t *baseXWithHalf = nullptr, int32_t *baseYWithHalf = nullptr) const; + + // アフィン変換付きプル処理(スキャンライン専用) + RenderResponse &pullProcessWithAffine(const RenderRequest &request); }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -189,238 +219,222 @@ namespace FLEXIMG_NAMESPACE { // SourceNode - Template Method フック実装 // ============================================================================ -PrepareResponse SourceNode::onPullPrepare(const PrepareRequest &request) { - // 下流からの希望フォーマットを保存(将来のフォーマット最適化用) - preferredFormat_ = request.preferredFormat; +PrepareResponse SourceNode::onPullPrepare(const PrepareRequest &request) +{ + // 下流からの希望フォーマットを保存(将来のフォーマット最適化用) + preferredFormat_ = request.preferredFormat; + + // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) + dataRangeCache_.invalidate(); - // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) - dataRangeCache_.invalidate(); + // Prepare時のoriginを保存(Process時の差分計算用) + prepareOriginX_ = request.origin.x; + prepareOriginY_ = request.origin.y; - // Prepare時のoriginを保存(Process時の差分計算用) - prepareOriginX_ = request.origin.x; - prepareOriginY_ = request.origin.y; + // 常に合成行列を計算し、アフィン事前計算を実行 + // request.affineMatrix経由の平行移動も含めて一貫した座標計算を行う + AffineMatrix combinedMatrix; + if (request.hasAffine) { + combinedMatrix = request.affineMatrix * localMatrix_; + } else { + combinedMatrix = localMatrix_; // 無変換時は単位行列 + } - // 常に合成行列を計算し、アフィン事前計算を実行 - // request.affineMatrix経由の平行移動も含めて一貫した座標計算を行う - AffineMatrix combinedMatrix; - if (request.hasAffine) { - combinedMatrix = request.affineMatrix * localMatrix_; - } else { - combinedMatrix = localMatrix_; // 無変換時は単位行列 - } + // 逆行列とピクセル中心オフセットを計算 + affine_ = precomputeInverseAffine(combinedMatrix); + + if (affine_.isValid()) { + const int32_t invA = affine_.invMatrix.a; + const int32_t invB = affine_.invMatrix.b; + const int32_t invC = affine_.invMatrix.c; + const int32_t invD = affine_.invMatrix.d; + + // pivot は既に Q16.16 なのでそのまま使用 + const int32_t srcPivotXFixed16 = pivotX_; + const int32_t srcPivotYFixed16 = pivotY_; + + // prepareOrigin を逆行列で変換(Prepare時に1回だけ計算) + // Q16.16 × Q16.16 = Q32.32、右シフトで Q16.16 に戻す + const int32_t prepareOffsetX = static_cast( + (static_cast(prepareOriginX_) * invA + static_cast(prepareOriginY_) * invB) >> + INT_FIXED_SHIFT); + const int32_t prepareOffsetY = static_cast( + (static_cast(prepareOriginX_) * invC + static_cast(prepareOriginY_) * invD) >> + INT_FIXED_SHIFT); + + // バイリニア補間かどうかで有効範囲とオフセットが異なる + // copyQuadDDA対応フォーマットならバイリニア可能(出力はRGBA8_Straight) + const bool useBilinear = + (interpolationMode_ == InterpolationMode::Bilinear) && source_.formatID && source_.formatID->copyQuadDDA; + + if (useBilinear) { + // バイリニア: 有効範囲はNearest同様 srcSize + // 境界外ピクセルは copyQuadDDA の edgeFlags で透明として補間 + fpWidth_ = source_.width << INT_FIXED_SHIFT; + fpHeight_ = source_.height << INT_FIXED_SHIFT; + + // バイリニア: edgeFadeFlagsに応じて各辺の範囲を拡張 + // フェード有効な辺のみ halfPixel 分拡張(フェードアウト領域用) + // invA/invCの符号によって、どの辺がstart/endに対応するか変わる + constexpr int_fixed halfPixel = 1 << (INT_FIXED_SHIFT - 1); + + // X方向のフェード拡張 + int32_t hpAStart = 0, hpAEnd = 0; + if (invA >= 0) { + // 非反転: xs1_はLeft側、xs2_はRight側 + if (edgeFadeFlags_ & EdgeFade_Left) hpAStart = halfPixel; + if (edgeFadeFlags_ & EdgeFade_Right) hpAEnd = halfPixel; + } else { + // 反転: xs1_はRight側、xs2_はLeft側 + if (edgeFadeFlags_ & EdgeFade_Right) hpAStart = -halfPixel; + if (edgeFadeFlags_ & EdgeFade_Left) hpAEnd = -halfPixel; + } + + // Y方向のフェード拡張 + int32_t hpCStart = 0, hpCEnd = 0; + if (invC >= 0) { + // 非反転: ys1_はTop側、ys2_はBottom側 + if (edgeFadeFlags_ & EdgeFade_Top) hpCStart = halfPixel; + if (edgeFadeFlags_ & EdgeFade_Bottom) hpCEnd = halfPixel; + } else { + // 反転: ys1_はBottom側、ys2_はTop側 + if (edgeFadeFlags_ & EdgeFade_Bottom) hpCStart = -halfPixel; + if (edgeFadeFlags_ & EdgeFade_Top) hpCEnd = -halfPixel; + } + + xs1_ = invA + (invA < 0 ? fpWidth_ : -1) - hpAStart; + xs2_ = invA + (invA < 0 ? 0 : (fpWidth_ - 1)) + hpAEnd; + ys1_ = invC + (invC < 0 ? fpHeight_ : -1) - hpCStart; + ys2_ = invC + (invC < 0 ? 0 : (fpHeight_ - 1)) + hpCEnd; + + useBilinear_ = true; + } else { + // 最近傍: pivot の小数部を保持 + fpWidth_ = source_.width << INT_FIXED_SHIFT; + fpHeight_ = source_.height << INT_FIXED_SHIFT; + + xs1_ = invA + (invA < 0 ? fpWidth_ : -1); + xs2_ = invA + (invA < 0 ? 0 : (fpWidth_ - 1)); + ys1_ = invC + (invC < 0 ? fpHeight_ : -1); + ys2_ = invC + (invC < 0 ? 0 : (fpHeight_ - 1)); + + useBilinear_ = false; + } + + // baseTx/Ty は バイリニア・最近傍共通の計算式 + baseTxWithOffsets_ = + affine_.invTxFixed + srcPivotXFixed16 + affine_.rowOffsetX + affine_.dxOffsetX + prepareOffsetX; + baseTyWithOffsets_ = + affine_.invTyFixed + srcPivotYFixed16 + affine_.rowOffsetY + affine_.dxOffsetY + prepareOffsetY; + + // DDA増分に基づく最適化判定 + // 等倍表示相当(逆行列2x2部分が単位行列)かつ最近傍の場合、 + // DDA をスキップし、高速な非アフィンパス(subView参照)を使用 + // バイリニア補間時はedgeFade等の処理にDDAが必要なためスキップしない + constexpr int_fixed one = 1 << INT_FIXED_SHIFT; + bool isTranslationOnly = !useBilinear_ && invA == one && invD == one && invB == 0 && invC == 0; + + hasAffine_ = !isTranslationOnly; + } else { + // 逆行列が無効(特異行列) + hasAffine_ = true; + } - // 逆行列とピクセル中心オフセットを計算 - affine_ = precomputeInverseAffine(combinedMatrix); + // SourceNodeは終端なので上流への伝播なし + // 出力側で必要なAABBを計算(常にcalcAffineAABBを使用) + PrepareResponse result; + result.status = PrepareStatus::Prepared; + result.preferredFormat = source_.formatID; + + // バイリニア補間のフェード領域分を考慮した入力矩形 + // フェード有効な辺は0.5ピクセル拡張される + float aabbWidth = static_cast(source_.width); + float aabbHeight = static_cast(source_.height); + int_fixed aabbPivotX = pivotX_; + int_fixed aabbPivotY = pivotY_; + if (useBilinear_) { + constexpr float half = 0.5f; + constexpr int_fixed halfFixed = 1 << (INT_FIXED_SHIFT - 1); + if (edgeFadeFlags_ & EdgeFade_Left) { + aabbWidth += half; + aabbPivotX += halfFixed; + } + if (edgeFadeFlags_ & EdgeFade_Right) { + aabbWidth += half; + } + if (edgeFadeFlags_ & EdgeFade_Top) { + aabbHeight += half; + aabbPivotY += halfFixed; + } + if (edgeFadeFlags_ & EdgeFade_Bottom) { + aabbHeight += half; + } + } - if (affine_.isValid()) { - const int32_t invA = affine_.invMatrix.a; - const int32_t invB = affine_.invMatrix.b; - const int32_t invC = affine_.invMatrix.c; - const int32_t invD = affine_.invMatrix.d; + calcAffineAABB(aabbWidth, aabbHeight, {aabbPivotX, aabbPivotY}, combinedMatrix, result.width, result.height, + result.origin); - // pivot は既に Q16.16 なのでそのまま使用 - const int32_t srcPivotXFixed16 = pivotX_; - const int32_t srcPivotYFixed16 = pivotY_; - - // prepareOrigin を逆行列で変換(Prepare時に1回だけ計算) - // Q16.16 × Q16.16 = Q32.32、右シフトで Q16.16 に戻す - const int32_t prepareOffsetX = - static_cast((static_cast(prepareOriginX_) * invA + - static_cast(prepareOriginY_) * invB) >> - INT_FIXED_SHIFT); - const int32_t prepareOffsetY = - static_cast((static_cast(prepareOriginX_) * invC + - static_cast(prepareOriginY_) * invD) >> - INT_FIXED_SHIFT); - - // バイリニア補間かどうかで有効範囲とオフセットが異なる - // copyQuadDDA対応フォーマットならバイリニア可能(出力はRGBA8_Straight) - const bool useBilinear = - (interpolationMode_ == InterpolationMode::Bilinear) && - source_.formatID && source_.formatID->copyQuadDDA; - - if (useBilinear) { - // バイリニア: 有効範囲はNearest同様 srcSize - // 境界外ピクセルは copyQuadDDA の edgeFlags で透明として補間 - fpWidth_ = source_.width << INT_FIXED_SHIFT; - fpHeight_ = source_.height << INT_FIXED_SHIFT; - - // バイリニア: edgeFadeFlagsに応じて各辺の範囲を拡張 - // フェード有効な辺のみ halfPixel 分拡張(フェードアウト領域用) - // invA/invCの符号によって、どの辺がstart/endに対応するか変わる - constexpr int_fixed halfPixel = 1 << (INT_FIXED_SHIFT - 1); - - // X方向のフェード拡張 - int32_t hpAStart = 0, hpAEnd = 0; - if (invA >= 0) { - // 非反転: xs1_はLeft側、xs2_はRight側 - if (edgeFadeFlags_ & EdgeFade_Left) - hpAStart = halfPixel; - if (edgeFadeFlags_ & EdgeFade_Right) - hpAEnd = halfPixel; - } else { - // 反転: xs1_はRight側、xs2_はLeft側 - if (edgeFadeFlags_ & EdgeFade_Right) - hpAStart = -halfPixel; - if (edgeFadeFlags_ & EdgeFade_Left) - hpAEnd = -halfPixel; - } - - // Y方向のフェード拡張 - int32_t hpCStart = 0, hpCEnd = 0; - if (invC >= 0) { - // 非反転: ys1_はTop側、ys2_はBottom側 - if (edgeFadeFlags_ & EdgeFade_Top) - hpCStart = halfPixel; - if (edgeFadeFlags_ & EdgeFade_Bottom) - hpCEnd = halfPixel; - } else { - // 反転: ys1_はBottom側、ys2_はTop側 - if (edgeFadeFlags_ & EdgeFade_Bottom) - hpCStart = -halfPixel; - if (edgeFadeFlags_ & EdgeFade_Top) - hpCEnd = -halfPixel; - } - - xs1_ = invA + (invA < 0 ? fpWidth_ : -1) - hpAStart; - xs2_ = invA + (invA < 0 ? 0 : (fpWidth_ - 1)) + hpAEnd; - ys1_ = invC + (invC < 0 ? fpHeight_ : -1) - hpCStart; - ys2_ = invC + (invC < 0 ? 0 : (fpHeight_ - 1)) + hpCEnd; - - useBilinear_ = true; - } else { - // 最近傍: pivot の小数部を保持 - fpWidth_ = source_.width << INT_FIXED_SHIFT; - fpHeight_ = source_.height << INT_FIXED_SHIFT; - - xs1_ = invA + (invA < 0 ? fpWidth_ : -1); - xs2_ = invA + (invA < 0 ? 0 : (fpWidth_ - 1)); - ys1_ = invC + (invC < 0 ? fpHeight_ : -1); - ys2_ = invC + (invC < 0 ? 0 : (fpHeight_ - 1)); - - useBilinear_ = false; - } - - // baseTx/Ty は バイリニア・最近傍共通の計算式 - baseTxWithOffsets_ = affine_.invTxFixed + srcPivotXFixed16 + - affine_.rowOffsetX + affine_.dxOffsetX + - prepareOffsetX; - baseTyWithOffsets_ = affine_.invTyFixed + srcPivotYFixed16 + - affine_.rowOffsetY + affine_.dxOffsetY + - prepareOffsetY; - - // DDA増分に基づく最適化判定 - // 等倍表示相当(逆行列2x2部分が単位行列)かつ最近傍の場合、 - // DDA をスキップし、高速な非アフィンパス(subView参照)を使用 - // バイリニア補間時はedgeFade等の処理にDDAが必要なためスキップしない - constexpr int_fixed one = 1 << INT_FIXED_SHIFT; - bool isTranslationOnly = - !useBilinear_ && invA == one && invD == one && invB == 0 && invC == 0; - - hasAffine_ = !isTranslationOnly; - } else { - // 逆行列が無効(特異行列) - hasAffine_ = true; - } - - // SourceNodeは終端なので上流への伝播なし - // 出力側で必要なAABBを計算(常にcalcAffineAABBを使用) - PrepareResponse result; - result.status = PrepareStatus::Prepared; - result.preferredFormat = source_.formatID; - - // バイリニア補間のフェード領域分を考慮した入力矩形 - // フェード有効な辺は0.5ピクセル拡張される - float aabbWidth = static_cast(source_.width); - float aabbHeight = static_cast(source_.height); - int_fixed aabbPivotX = pivotX_; - int_fixed aabbPivotY = pivotY_; - if (useBilinear_) { - constexpr float half = 0.5f; - constexpr int_fixed halfFixed = 1 << (INT_FIXED_SHIFT - 1); - if (edgeFadeFlags_ & EdgeFade_Left) { - aabbWidth += half; - aabbPivotX += halfFixed; - } - if (edgeFadeFlags_ & EdgeFade_Right) { - aabbWidth += half; - } - if (edgeFadeFlags_ & EdgeFade_Top) { - aabbHeight += half; - aabbPivotY += halfFixed; - } - if (edgeFadeFlags_ & EdgeFade_Bottom) { - aabbHeight += half; - } - } - - calcAffineAABB(aabbWidth, aabbHeight, {aabbPivotX, aabbPivotY}, - combinedMatrix, result.width, result.height, result.origin); - - return result; + return result; } -RenderResponse &SourceNode::onPullProcess(const RenderRequest &request) { - FLEXIMG_METRICS_SCOPE(NodeType::Source); - - if (!source_.isValid()) { - return makeEmptyResponse(request.origin); - } - - // アフィン変換が伝播されている場合はDDA処理 - if (hasAffine_) { - return pullProcessWithAffine(request); - } - - // アフィン事前計算値から座標を導出(DDAパスと同一の情報源) - // baseTxWithOffsets_ はPrepare時に合成行列から計算済みで、 - // request.affineMatrix経由の平行移動も含まれている - const int32_t deltaX = from_fixed(request.origin.x - prepareOriginX_); - const int32_t deltaY = from_fixed(request.origin.y - prepareOriginY_); - const int32_t baseX = baseTxWithOffsets_ + deltaX * affine_.invMatrix.a + - deltaY * affine_.invMatrix.b; - const int32_t baseY = baseTyWithOffsets_ + deltaX * affine_.invMatrix.c + - deltaY * affine_.invMatrix.d; - - // srcBase: 出力dx=0に対応するソースピクセルインデックス - int32_t srcBaseX = from_fixed_floor(baseX); - int32_t srcBaseY = from_fixed_floor(baseY); - - // 有効範囲: srcBase + dx が [0, srcSize) に収まる dx の範囲 - // srcBase + dxStart >= 0 → dxStart >= -srcBaseX - // srcBase + dxEnd < srcSize → dxEnd < srcSize - srcBaseX - auto dxStartX = std::max(0, -srcBaseX); - auto dxEndX = std::min(request.width, source_.width - srcBaseX); - auto dxStartY = std::max(0, -srcBaseY); - auto dxEndY = std::min(request.height, source_.height - srcBaseY); - - if (dxStartX >= dxEndX || dxStartY >= dxEndY) { - return makeEmptyResponse(request.origin); - } - - int32_t validW = dxEndX - dxStartX; - int32_t validH = dxEndY - dxStartY; - - // サブビューの参照モードImageBufferを作成(メモリ確保なし) - auto srcX = static_cast(srcBaseX + dxStartX); - auto srcY = static_cast(srcBaseY + dxStartY); - ImageBuffer result(view_ops::subView(source_, srcX, srcY, - static_cast(validW), - static_cast(validH))); - // パレット情報を出力ImageBufferに設定 - if (palette_) { - result.setPalette(palette_); - } - // カラーキー情報を出力ImageBufferに設定 - if (colorKeyRGBA8_ != colorKeyReplace_) { - result.auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; - result.auxInfo().colorKeyReplace = colorKeyReplace_; - } - - // origin = リクエストグリッドに整列(アフィンパスと同形式) - Point adjustedOrigin = {request.origin.x + to_fixed(dxStartX), - request.origin.y + to_fixed(dxStartY)}; - return makeResponse(std::move(result), adjustedOrigin); +RenderResponse &SourceNode::onPullProcess(const RenderRequest &request) +{ + FLEXIMG_METRICS_SCOPE(NodeType::Source); + + if (!source_.isValid()) { + return makeEmptyResponse(request.origin); + } + + // アフィン変換が伝播されている場合はDDA処理 + if (hasAffine_) { + return pullProcessWithAffine(request); + } + + // アフィン事前計算値から座標を導出(DDAパスと同一の情報源) + // baseTxWithOffsets_ はPrepare時に合成行列から計算済みで、 + // request.affineMatrix経由の平行移動も含まれている + const int32_t deltaX = from_fixed(request.origin.x - prepareOriginX_); + const int32_t deltaY = from_fixed(request.origin.y - prepareOriginY_); + const int32_t baseX = baseTxWithOffsets_ + deltaX * affine_.invMatrix.a + deltaY * affine_.invMatrix.b; + const int32_t baseY = baseTyWithOffsets_ + deltaX * affine_.invMatrix.c + deltaY * affine_.invMatrix.d; + + // srcBase: 出力dx=0に対応するソースピクセルインデックス + int32_t srcBaseX = from_fixed_floor(baseX); + int32_t srcBaseY = from_fixed_floor(baseY); + + // 有効範囲: srcBase + dx が [0, srcSize) に収まる dx の範囲 + // srcBase + dxStart >= 0 → dxStart >= -srcBaseX + // srcBase + dxEnd < srcSize → dxEnd < srcSize - srcBaseX + auto dxStartX = std::max(0, -srcBaseX); + auto dxEndX = std::min(request.width, source_.width - srcBaseX); + auto dxStartY = std::max(0, -srcBaseY); + auto dxEndY = std::min(request.height, source_.height - srcBaseY); + + if (dxStartX >= dxEndX || dxStartY >= dxEndY) { + return makeEmptyResponse(request.origin); + } + + int32_t validW = dxEndX - dxStartX; + int32_t validH = dxEndY - dxStartY; + + // サブビューの参照モードImageBufferを作成(メモリ確保なし) + auto srcX = static_cast(srcBaseX + dxStartX); + auto srcY = static_cast(srcBaseY + dxStartY); + ImageBuffer result( + view_ops::subView(source_, srcX, srcY, static_cast(validW), static_cast(validH))); + // パレット情報を出力ImageBufferに設定 + if (palette_) { + result.setPalette(palette_); + } + // カラーキー情報を出力ImageBufferに設定 + if (colorKeyRGBA8_ != colorKeyReplace_) { + result.auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; + result.auxInfo().colorKeyReplace = colorKeyReplace_; + } + + // origin = リクエストグリッドに整列(アフィンパスと同形式) + Point adjustedOrigin = {request.origin.x + to_fixed(dxStartX), request.origin.y + to_fixed(dxStartY)}; + return makeResponse(std::move(result), adjustedOrigin); } // ============================================================================ @@ -429,218 +443,203 @@ RenderResponse &SourceNode::onPullProcess(const RenderRequest &request) { // スキャンライン有効範囲を計算(getDataRange/pullProcessWithAffineで共用) // 戻り値: true=有効範囲あり, false=有効範囲なし -bool SourceNode::calcScanlineRange(const RenderRequest &request, - int32_t &dxStart, int32_t &dxEnd, - int32_t *outBaseX, int32_t *outBaseY) const { - // 特異行列チェック - if (!affine_.isValid()) { - return false; - } - - // LovyanGFX/pixel_image.hpp 方式: 事前計算済み境界値を使った範囲計算 - const int32_t invA = affine_.invMatrix.a; - const int32_t invB = affine_.invMatrix.b; - const int32_t invC = affine_.invMatrix.c; - const int32_t invD = affine_.invMatrix.d; - - // Prepare時のoriginからの差分(ピクセル単位の整数) - // RendererNodeはピクセル単位でタイル分割するため、差分は常に整数 - const int32_t deltaX = from_fixed(request.origin.x - prepareOriginX_); - const int32_t deltaY = from_fixed(request.origin.y - prepareOriginY_); - - // 整数 × Q16.16 = Q16.16(int32_t範囲内) - // baseTxWithOffsets_ は Prepare時のoriginに対応した値として事前計算済み - const int32_t baseX = baseTxWithOffsets_ + deltaX * invA + deltaY * invB; - const int32_t baseY = baseTyWithOffsets_ + deltaX * invC + deltaY * invD; - - int32_t left = 0; - int32_t right = request.width; - - if (invA) { - left = std::max(left, (xs1_ - baseX) / invA); - right = std::min(right, (xs2_ - baseX) / invA); - } else if (static_cast(baseX) >= static_cast(fpWidth_)) { - left = 1; - right = 0; - } - - if (invC) { - left = std::max(left, (ys1_ - baseY) / invC); - right = std::min(right, (ys2_ - baseY) / invC); - } else if (static_cast(baseY) >= static_cast(fpHeight_)) { - left = 1; - right = 0; - } - - dxStart = left; - dxEnd = right - 1; // right は排他的なので -1 - - // DDA用ベース座標を出力(オプショナル) - if (outBaseX) - *outBaseX = baseX; - if (outBaseY) - *outBaseY = baseY; - - return dxStart <= dxEnd; +bool SourceNode::calcScanlineRange(const RenderRequest &request, int32_t &dxStart, int32_t &dxEnd, int32_t *outBaseX, + int32_t *outBaseY) const +{ + // 特異行列チェック + if (!affine_.isValid()) { + return false; + } + + // LovyanGFX/pixel_image.hpp 方式: 事前計算済み境界値を使った範囲計算 + const int32_t invA = affine_.invMatrix.a; + const int32_t invB = affine_.invMatrix.b; + const int32_t invC = affine_.invMatrix.c; + const int32_t invD = affine_.invMatrix.d; + + // Prepare時のoriginからの差分(ピクセル単位の整数) + // RendererNodeはピクセル単位でタイル分割するため、差分は常に整数 + const int32_t deltaX = from_fixed(request.origin.x - prepareOriginX_); + const int32_t deltaY = from_fixed(request.origin.y - prepareOriginY_); + + // 整数 × Q16.16 = Q16.16(int32_t範囲内) + // baseTxWithOffsets_ は Prepare時のoriginに対応した値として事前計算済み + const int32_t baseX = baseTxWithOffsets_ + deltaX * invA + deltaY * invB; + const int32_t baseY = baseTyWithOffsets_ + deltaX * invC + deltaY * invD; + + int32_t left = 0; + int32_t right = request.width; + + if (invA) { + left = std::max(left, (xs1_ - baseX) / invA); + right = std::min(right, (xs2_ - baseX) / invA); + } else if (static_cast(baseX) >= static_cast(fpWidth_)) { + left = 1; + right = 0; + } + + if (invC) { + left = std::max(left, (ys1_ - baseY) / invC); + right = std::min(right, (ys2_ - baseY) / invC); + } else if (static_cast(baseY) >= static_cast(fpHeight_)) { + left = 1; + right = 0; + } + + dxStart = left; + dxEnd = right - 1; // right は排他的なので -1 + + // DDA用ベース座標を出力(オプショナル) + if (outBaseX) *outBaseX = baseX; + if (outBaseY) *outBaseY = baseY; + + return dxStart <= dxEnd; } // getDataRange: スキャンライン単位の正確なデータ範囲を返す // アフィン変換時はcalcScanlineRangeで厳密な有効範囲を計算 // 同一リクエストの重複呼び出しはキャッシュで高速化 -DataRange SourceNode::getDataRange(const RenderRequest &request) const { - // アフィン変換がない場合はAABBベースで十分(正確) - if (!hasAffine_) { - return prepareResponse_.getDataRange(request); - } - - // キャッシュヒットチェック(同一スキャンラインの重複呼び出し対応) - DataRange cached; - if (dataRangeCache_.tryGet(request, cached)) { - return cached; - } - - // calcScanlineRangeで正確な有効範囲を計算 - int32_t dxStart = 0, dxEnd = 0; - DataRange result; - if (calcScanlineRange(request, dxStart, dxEnd, nullptr, nullptr)) { - result = DataRange{static_cast(dxStart), - static_cast(dxEnd + 1)}; - } else { - result = DataRange{0, 0}; - } - - // キャッシュ更新 - dataRangeCache_.set(request, result); - - return result; +DataRange SourceNode::getDataRange(const RenderRequest &request) const +{ + // アフィン変換がない場合はAABBベースで十分(正確) + if (!hasAffine_) { + return prepareResponse_.getDataRange(request); + } + + // キャッシュヒットチェック(同一スキャンラインの重複呼び出し対応) + DataRange cached; + if (dataRangeCache_.tryGet(request, cached)) { + return cached; + } + + // calcScanlineRangeで正確な有効範囲を計算 + int32_t dxStart = 0, dxEnd = 0; + DataRange result; + if (calcScanlineRange(request, dxStart, dxEnd, nullptr, nullptr)) { + result = DataRange{static_cast(dxStart), static_cast(dxEnd + 1)}; + } else { + result = DataRange{0, 0}; + } + + // キャッシュ更新 + dataRangeCache_.set(request, result); + + return result; } // アフィン変換付きプル処理(スキャンライン専用) // 前提: request.height == 1(RendererNodeはスキャンライン単位で処理) // 有効範囲のみのバッファを返し、範囲外の0データを下流に送らない -RenderResponse & -SourceNode::pullProcessWithAffine(const RenderRequest &request) { - // スキャンライン有効範囲を計算 - int32_t dxStart = 0, dxEnd = 0, baseX = 0, baseY = 0; - if (!calcScanlineRange(request, dxStart, dxEnd, &baseX, &baseY)) { - return makeEmptyResponse(request.origin); - } - - // originを有効範囲に合わせて調整 - // dxStart分だけ右にオフセット(バッファ左端のワールド座標) - Point adjustedOrigin = {request.origin.x + to_fixed(dxStart), - request.origin.y}; - - // 空のResponseを取得し、バッファを直接作成(ムーブなし) - int validWidth = dxEnd - dxStart + 1; - RenderResponse &resp = makeEmptyResponse(adjustedOrigin); - // 出力フォーマット決定: - // - 1chバイリニア対応フォーマット(Alpha8等): ソースフォーマット直接出力 - // - その他のバイリニア: RGBA8_Straight出力 - // - 最近傍: ソースフォーマット出力(ただしbit-packedはIndex8に展開) - PixelFormatID outFormat; - if (useBilinear_) { - outFormat = - view_ops::canUseSingleChannelBilinear(source_.formatID, edgeFadeFlags_) - ? source_.formatID - : PixelFormatIDs::RGBA8_Straight; - } else { - // bit-packed形式の場合、DDAはIndex8形式で出力するため出力フォーマットをIndex8に - if (source_.formatID && source_.formatID->pixelsPerUnit > 1) { - outFormat = PixelFormatIDs::Index8; +RenderResponse &SourceNode::pullProcessWithAffine(const RenderRequest &request) +{ + // スキャンライン有効範囲を計算 + int32_t dxStart = 0, dxEnd = 0, baseX = 0, baseY = 0; + if (!calcScanlineRange(request, dxStart, dxEnd, &baseX, &baseY)) { + return makeEmptyResponse(request.origin); + } + + // originを有効範囲に合わせて調整 + // dxStart分だけ右にオフセット(バッファ左端のワールド座標) + Point adjustedOrigin = {request.origin.x + to_fixed(dxStart), request.origin.y}; + + // 空のResponseを取得し、バッファを直接作成(ムーブなし) + int validWidth = dxEnd - dxStart + 1; + RenderResponse &resp = makeEmptyResponse(adjustedOrigin); + // 出力フォーマット決定: + // - 1chバイリニア対応フォーマット(Alpha8等): ソースフォーマット直接出力 + // - その他のバイリニア: RGBA8_Straight出力 + // - 最近傍: ソースフォーマット出力(ただしbit-packedはIndex8に展開) + PixelFormatID outFormat; + if (useBilinear_) { + outFormat = view_ops::canUseSingleChannelBilinear(source_.formatID, edgeFadeFlags_) + ? source_.formatID + : PixelFormatIDs::RGBA8_Straight; } else { - outFormat = source_.formatID; + // bit-packed形式の場合、DDAはIndex8形式で出力するため出力フォーマットをIndex8に + if (source_.formatID && source_.formatID->pixelsPerUnit > 1) { + outFormat = PixelFormatIDs::Index8; + } else { + outFormat = source_.formatID; + } } - } - ImageBuffer *output = - resp.createBuffer(validWidth, 1, outFormat, InitPolicy::Uninitialized); + ImageBuffer *output = resp.createBuffer(validWidth, 1, outFormat, InitPolicy::Uninitialized); - if (!output) { - return resp; // バッファ作成失敗時は空のResponseを返す - } + if (!output) { + return resp; // バッファ作成失敗時は空のResponseを返す + } - // バッファにワールド座標originを設定(makeResponseを使わないパス) - output->setOrigin(adjustedOrigin); + // バッファにワールド座標originを設定(makeResponseを使わないパス) + output->setOrigin(adjustedOrigin); #ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().nodes[NodeType::Source].recordAlloc( - output->totalBytes(), output->width(), output->height()); + PerfMetrics::instance().nodes[NodeType::Source].recordAlloc(output->totalBytes(), output->width(), + output->height()); #endif - // DDA転写(1行のみ) - const int32_t invA = affine_.invMatrix.a; - const int32_t invC = affine_.invMatrix.c; - int32_t srcX_fixed = invA * dxStart + baseX; - int32_t srcY_fixed = invC * dxStart + baseY; - - void *dstRow = output->data(); - - // ViewPortのx,yオフセットをQ16.16固定小数点に変換 - int_fixed offsetX = static_cast(source_.x) << INT_FIXED_SHIFT; - int_fixed offsetY = static_cast(source_.y) << INT_FIXED_SHIFT; + // DDA転写(1行のみ) + const int32_t invA = affine_.invMatrix.a; + const int32_t invC = affine_.invMatrix.c; + int32_t srcX_fixed = invA * dxStart + baseX; + int32_t srcY_fixed = invC * dxStart + baseY; + + void *dstRow = output->data(); + + // ViewPortのx,yオフセットをQ16.16固定小数点に変換 + int_fixed offsetX = static_cast(source_.x) << INT_FIXED_SHIFT; + int_fixed offsetY = static_cast(source_.y) << INT_FIXED_SHIFT; + + if (useBilinear_) { + // バイリニア補間(出力はRGBA8_Straight) + // 0.5ピクセル減算(ピクセル中心→左上基準への変換) + constexpr int_fixed halfPixel = 1 << (INT_FIXED_SHIFT - 1); + // パレット情報をPixelAuxInfoとして渡す(Index8のパレット展開用) + PixelAuxInfo auxInfo; + if (palette_) { + auxInfo.palette = palette_.data; + auxInfo.paletteFormat = palette_.format; + auxInfo.paletteColorCount = palette_.colorCount; + } + if (colorKeyRGBA8_ != colorKeyReplace_) { + auxInfo.colorKeyRGBA8 = colorKeyRGBA8_; + auxInfo.colorKeyReplace = colorKeyReplace_; + } + const PixelAuxInfo *auxPtr = + (auxInfo.palette || auxInfo.colorKeyRGBA8 != auxInfo.colorKeyReplace) ? &auxInfo : nullptr; + view_ops::copyRowDDABilinear(dstRow, source_, validWidth, srcX_fixed + offsetX - halfPixel, + srcY_fixed + offsetY - halfPixel, invA, invC, edgeFadeFlags_, auxPtr); + } else { + // 最近傍補間(BPP分岐は関数内部で実施) + // view_ops::copyRowDDA(dstRow, source_, validWidth, + // srcX_fixed, srcY_fixed, invA, invC); + + // DDAParam を構築(bit-packed形式は境界チェックにsrcWidth/srcHeightを使用) + // ViewPortのx,yオフセットを加算 + DDAParam param = { + source_.stride, source_.width, source_.height, srcX_fixed + offsetX, srcY_fixed + offsetY, invA, + invC, nullptr, nullptr}; + + // フォーマットの関数ポインタを呼び出し + if (source_.formatID && source_.formatID->copyRowDDA) { + source_.formatID->copyRowDDA(static_cast(dstRow), static_cast(source_.data), + validWidth, ¶m); + } + } - if (useBilinear_) { - // バイリニア補間(出力はRGBA8_Straight) - // 0.5ピクセル減算(ピクセル中心→左上基準への変換) - constexpr int_fixed halfPixel = 1 << (INT_FIXED_SHIFT - 1); - // パレット情報をPixelAuxInfoとして渡す(Index8のパレット展開用) - PixelAuxInfo auxInfo; + // パレット情報を出力ImageBufferに設定 if (palette_) { - auxInfo.palette = palette_.data; - auxInfo.paletteFormat = palette_.format; - auxInfo.paletteColorCount = palette_.colorCount; + output->setPalette(palette_); } + // カラーキー情報を出力ImageBufferに設定 if (colorKeyRGBA8_ != colorKeyReplace_) { - auxInfo.colorKeyRGBA8 = colorKeyRGBA8_; - auxInfo.colorKeyReplace = colorKeyReplace_; - } - const PixelAuxInfo *auxPtr = - (auxInfo.palette || auxInfo.colorKeyRGBA8 != auxInfo.colorKeyReplace) - ? &auxInfo - : nullptr; - view_ops::copyRowDDABilinear( - dstRow, source_, validWidth, srcX_fixed + offsetX - halfPixel, - srcY_fixed + offsetY - halfPixel, invA, invC, edgeFadeFlags_, auxPtr); - } else { - // 最近傍補間(BPP分岐は関数内部で実施) - // view_ops::copyRowDDA(dstRow, source_, validWidth, - // srcX_fixed, srcY_fixed, invA, invC); - - // DDAParam を構築(bit-packed形式は境界チェックにsrcWidth/srcHeightを使用) - // ViewPortのx,yオフセットを加算 - DDAParam param = {source_.stride, - source_.width, - source_.height, - srcX_fixed + offsetX, - srcY_fixed + offsetY, - invA, - invC, - nullptr, - nullptr}; - - // フォーマットの関数ポインタを呼び出し - if (source_.formatID && source_.formatID->copyRowDDA) { - source_.formatID->copyRowDDA(static_cast(dstRow), - static_cast(source_.data), - validWidth, ¶m); - } - } - - // パレット情報を出力ImageBufferに設定 - if (palette_) { - output->setPalette(palette_); - } - // カラーキー情報を出力ImageBufferに設定 - if (colorKeyRGBA8_ != colorKeyReplace_) { - output->auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; - output->auxInfo().colorKeyReplace = colorKeyReplace_; - } - - return resp; + output->auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; + output->auxInfo().colorKeyReplace = colorKeyReplace_; + } + + return resp; } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_SOURCE_NODE_H +#endif // FLEXIMG_SOURCE_NODE_H diff --git a/src/fleximg/nodes/vertical_blur_node.h b/src/fleximg/nodes/vertical_blur_node.h index 108b8e5..60583fa 100644 --- a/src/fleximg/nodes/vertical_blur_node.h +++ b/src/fleximg/nodes/vertical_blur_node.h @@ -5,7 +5,7 @@ #include "../core/perf_metrics.h" #include "../image/image_buffer.h" #include -#include // for int32_t, uint32_t +#include // for int32_t, uint32_t #include #include @@ -46,155 +46,167 @@ namespace FLEXIMG_NAMESPACE { class VerticalBlurNode : public Node { public: - VerticalBlurNode() { initPorts(1, 1); } + VerticalBlurNode() + { + initPorts(1, 1); + } - // ======================================== - // パラメータ設定 - // ======================================== + // ======================================== + // パラメータ設定 + // ======================================== - // パラメータ上限 - static constexpr int kMaxRadius = 127; // 実用上十分、メモリ消費も許容範囲 - static constexpr int kMaxPasses = 3; // ガウシアン近似に十分 + // パラメータ上限 + static constexpr int kMaxRadius = 127; // 実用上十分、メモリ消費も許容範囲 + static constexpr int kMaxPasses = 3; // ガウシアン近似に十分 - void setRadius(int_fast16_t radius) { - radius_ = static_cast((radius < 0) ? 0 - : (radius > kMaxRadius) ? kMaxRadius - : radius); - } + void setRadius(int_fast16_t radius) + { + radius_ = static_cast((radius < 0) ? 0 : (radius > kMaxRadius) ? kMaxRadius : radius); + } - void setPasses(int_fast16_t passes) { - passes_ = static_cast((passes < 1) ? 1 - : (passes > kMaxPasses) ? kMaxPasses - : passes); - } + void setPasses(int_fast16_t passes) + { + passes_ = static_cast((passes < 1) ? 1 : (passes > kMaxPasses) ? kMaxPasses : passes); + } - int16_t radius() const { return radius_; } - int16_t passes() const { return passes_; } - int_fast16_t kernelSize() const { return radius_ * 2 + 1; } - int_fast16_t totalKernelSize() const { return radius_ * 2 * passes_ + 1; } + int16_t radius() const + { + return radius_; + } + int16_t passes() const + { + return passes_; + } + int_fast16_t kernelSize() const + { + return radius_ * 2 + 1; + } + int_fast16_t totalKernelSize() const + { + return radius_ * 2 * passes_ + 1; + } - // ======================================== - // Node インターフェース - // ======================================== + // ======================================== + // Node インターフェース + // ======================================== - const char *name() const override { return "VerticalBlurNode"; } + const char *name() const override + { + return "VerticalBlurNode"; + } - // getDataRange: 上下radius*passes行の上流DataRange和集合を返す - DataRange getDataRange(const RenderRequest &request) const override; + // getDataRange: 上下radius*passes行の上流DataRange和集合を返す + DataRange getDataRange(const RenderRequest &request) const override; - // 準備・終了処理(pull型用) - void prepare(const RenderRequest &screenInfo) override; - void finalize() override; + // 準備・終了処理(pull型用) + void prepare(const RenderRequest &screenInfo) override; + void finalize() override; - // Template Method フック - PrepareResponse onPullPrepare(const PrepareRequest &request) override; - PrepareResponse onPushPrepare(const PrepareRequest &request) override; - void onPushProcess(RenderResponse &input, - const RenderRequest &request) override; - void onPushFinalize() override; + // Template Method フック + PrepareResponse onPullPrepare(const PrepareRequest &request) override; + PrepareResponse onPushPrepare(const PrepareRequest &request) override; + void onPushProcess(RenderResponse &input, const RenderRequest &request) override; + void onPushFinalize() override; protected: - int nodeTypeForMetrics() const override { return NodeType::VerticalBlur; } - RenderResponse &onPullProcess(const RenderRequest &request) override; + int nodeTypeForMetrics() const override + { + return NodeType::VerticalBlur; + } + RenderResponse &onPullProcess(const RenderRequest &request) override; private: - int16_t radius_ = 5; - int16_t passes_ = 1; // 1-3の範囲、デフォルト1 - - // スクリーン情報 - int16_t screenWidth_ = 0; - int16_t screenHeight_ = 0; - Point screenOrigin_; - - // ======================================== - // パイプラインステージ構造体 - // ======================================== - // 各ステージが独立したキャッシュと列合計を持つ - // passes=3の場合、3つのステージがパイプライン接続される - struct BlurStage { - std::vector rowCache; // radius*2+1 行のキャッシュ - std::vector rowOriginX; // 各キャッシュ行のorigin.x(push型用) - std::vector rowDataRange; // 各キャッシュ行の有効範囲 - std::vector colSumR; // 列合計(R×A) - std::vector colSumG; // 列合計(G×A) - std::vector colSumB; // 列合計(B×A) - std::vector colSumA; // 列合計(A) - int32_t currentY = 0; // 現在のY座標(pull型用) - bool cacheReady = false; // キャッシュ初期化済みフラグ - - // push型用の状態 - int32_t pushInputY = 0; // 入力行カウント - int32_t pushOutputY = 0; // 出力行カウント - - void clear() { - rowCache.clear(); - rowOriginX.clear(); - rowDataRange.clear(); - colSumR.clear(); - colSumG.clear(); - colSumB.clear(); - colSumA.clear(); - currentY = 0; - cacheReady = false; - pushInputY = 0; - pushOutputY = 0; - } - }; - - // パイプラインステージ(passes個、passes=1でもstages_[0]を使用) - std::vector stages_; - int16_t cacheWidth_ = 0; - int_fixed cacheOriginX_ = 0; // キャッシュの基準X座標(pull型用) - int_fixed upstreamOriginX_ = - 0; // 上流pullProcessのorigin.x(radius=0と同じ出力用) - bool upstreamOriginXSet_ = false; // upstreamOriginX_が設定済みかどうか - - // 上流のY範囲(getDataRangeでのクエリY座標クランプ用) - int_fixed sourceOriginY_ = 0; // 上流のorigin.y(拡張前) - int16_t sourceHeight_ = 0; // 上流の高さ(拡張前) - - // push型処理用の状態 - int32_t pushInputY_ = 0; - int32_t pushOutputY_ = 0; - int16_t pushInputWidth_ = 0; - int16_t pushInputHeight_ = 0; - int16_t pushOutputHeight_ = 0; - int_fixed baseOriginX_ = 0; // 基準origin.x(pushPrepareで設定) - int_fixed pushInputOriginY_ = 0; - int_fixed lastInputOriginY_ = 0; - - // getDataRange/pullProcess 間のキャッシュ - struct DataRangeCache { - Point origin = {INT32_MIN, INT32_MIN}; // キャッシュキー(無効値で初期化) - int16_t startX = 0; - int16_t endX = 0; - }; - mutable DataRangeCache rangeCache_; - - // 内部実装(宣言のみ) - RenderResponse &pullProcessPipeline(Node *upstream, - const RenderRequest &request); - void updateStageCache(int_fast16_t stageIndex, Node *upstream, - const RenderRequest &request, int_fast16_t newY); - void fetchRowToStageCache(BlurStage &stage, Node *upstream, - const RenderRequest &request, int_fast16_t srcY, - int_fast16_t cacheIndex); - void fetchRowFromPrevStage(int_fast16_t stageIndex, Node *upstream, - const RenderRequest &request, int_fast16_t srcY, - int_fast16_t cacheIndex); - void updateStageColSum(BlurStage &stage, int_fast16_t cacheIndex, bool add); - void computeStageOutputRow(BlurStage &stage, ImageBuffer &output, - int_fast16_t width); - void initializeStage(BlurStage &stage, int_fast16_t width); - void initializeStages(int_fast16_t width); - void propagatePipelineStages(); - void emitBlurredLinePipeline(); - void storeInputRowToStageCache(BlurStage &stage, const ImageBuffer &input, - int_fast16_t cacheIndex, - int_fast16_t xOffset = 0); + int16_t radius_ = 5; + int16_t passes_ = 1; // 1-3の範囲、デフォルト1 + + // スクリーン情報 + int16_t screenWidth_ = 0; + int16_t screenHeight_ = 0; + Point screenOrigin_; + + // ======================================== + // パイプラインステージ構造体 + // ======================================== + // 各ステージが独立したキャッシュと列合計を持つ + // passes=3の場合、3つのステージがパイプライン接続される + struct BlurStage { + std::vector rowCache; // radius*2+1 行のキャッシュ + std::vector rowOriginX; // 各キャッシュ行のorigin.x(push型用) + std::vector rowDataRange; // 各キャッシュ行の有効範囲 + std::vector colSumR; // 列合計(R×A) + std::vector colSumG; // 列合計(G×A) + std::vector colSumB; // 列合計(B×A) + std::vector colSumA; // 列合計(A) + int32_t currentY = 0; // 現在のY座標(pull型用) + bool cacheReady = false; // キャッシュ初期化済みフラグ + + // push型用の状態 + int32_t pushInputY = 0; // 入力行カウント + int32_t pushOutputY = 0; // 出力行カウント + + void clear() + { + rowCache.clear(); + rowOriginX.clear(); + rowDataRange.clear(); + colSumR.clear(); + colSumG.clear(); + colSumB.clear(); + colSumA.clear(); + currentY = 0; + cacheReady = false; + pushInputY = 0; + pushOutputY = 0; + } + }; + + // パイプラインステージ(passes個、passes=1でもstages_[0]を使用) + std::vector stages_; + int16_t cacheWidth_ = 0; + int_fixed cacheOriginX_ = 0; // キャッシュの基準X座標(pull型用) + int_fixed upstreamOriginX_ = 0; // 上流pullProcessのorigin.x(radius=0と同じ出力用) + bool upstreamOriginXSet_ = false; // upstreamOriginX_が設定済みかどうか + + // 上流のY範囲(getDataRangeでのクエリY座標クランプ用) + int_fixed sourceOriginY_ = 0; // 上流のorigin.y(拡張前) + int16_t sourceHeight_ = 0; // 上流の高さ(拡張前) + + // push型処理用の状態 + int32_t pushInputY_ = 0; + int32_t pushOutputY_ = 0; + int16_t pushInputWidth_ = 0; + int16_t pushInputHeight_ = 0; + int16_t pushOutputHeight_ = 0; + int_fixed baseOriginX_ = 0; // 基準origin.x(pushPrepareで設定) + int_fixed pushInputOriginY_ = 0; + int_fixed lastInputOriginY_ = 0; + + // getDataRange/pullProcess 間のキャッシュ + struct DataRangeCache { + Point origin = {INT32_MIN, INT32_MIN}; // キャッシュキー(無効値で初期化) + int16_t startX = 0; + int16_t endX = 0; + }; + mutable DataRangeCache rangeCache_; + + // 内部実装(宣言のみ) + RenderResponse &pullProcessPipeline(Node *upstream, const RenderRequest &request); + void updateStageCache(int_fast16_t stageIndex, Node *upstream, const RenderRequest &request, int_fast16_t newY); + void fetchRowToStageCache(BlurStage &stage, Node *upstream, const RenderRequest &request, int_fast16_t srcY, + int_fast16_t cacheIndex); + void fetchRowFromPrevStage(int_fast16_t stageIndex, Node *upstream, const RenderRequest &request, int_fast16_t srcY, + int_fast16_t cacheIndex); + void updateStageColSum(BlurStage &stage, int_fast16_t cacheIndex, bool add); + void computeStageOutputRow(BlurStage &stage, ImageBuffer &output, int_fast16_t width); + void initializeStage(BlurStage &stage, int_fast16_t width); + void initializeStages(int_fast16_t width); + void propagatePipelineStages(); + void emitBlurredLinePipeline(); + void storeInputRowToStageCache(BlurStage &stage, const ImageBuffer &input, int_fast16_t cacheIndex, + int_fast16_t xOffset = 0); }; -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -207,775 +219,724 @@ namespace FLEXIMG_NAMESPACE { // 準備・終了処理 // ======================================== -void VerticalBlurNode::prepare(const RenderRequest &screenInfo) { - screenWidth_ = screenInfo.width; - screenHeight_ = screenInfo.height; - screenOrigin_ = screenInfo.origin; +void VerticalBlurNode::prepare(const RenderRequest &screenInfo) +{ + screenWidth_ = screenInfo.width; + screenHeight_ = screenInfo.height; + screenOrigin_ = screenInfo.origin; - // radius=0またはpasses=0の場合はキャッシュ不要 - if (radius_ == 0 || passes_ == 0) - return; + // radius=0またはpasses=0の場合はキャッシュ不要 + if (radius_ == 0 || passes_ == 0) return; - // パイプライン方式でキャッシュを初期化(passes=1でもstages_[0]を使用) - initializeStages(screenWidth_); + // パイプライン方式でキャッシュを初期化(passes=1でもstages_[0]を使用) + initializeStages(screenWidth_); #ifdef FLEXIMG_DEBUG_PERF_METRICS - // パイプライン方式: 各ステージ (radius*2+1)*width*4 + width*16 - size_t cacheBytes = static_cast(passes_) * - (static_cast(kernelSize()) * - static_cast(cacheWidth_) * 4 + - static_cast(cacheWidth_) * 4 * sizeof(uint32_t)); - PerfMetrics::instance().nodes[NodeType::VerticalBlur].recordAlloc( - cacheBytes, cacheWidth_, kernelSize() * passes_); + // パイプライン方式: 各ステージ (radius*2+1)*width*4 + width*16 + size_t cacheBytes = + static_cast(passes_) * (static_cast(kernelSize()) * static_cast(cacheWidth_) * 4 + + static_cast(cacheWidth_) * 4 * sizeof(uint32_t)); + PerfMetrics::instance().nodes[NodeType::VerticalBlur].recordAlloc(cacheBytes, cacheWidth_, kernelSize() * passes_); #endif } -void VerticalBlurNode::finalize() { - // パイプラインステージをクリア - for (auto &stage : stages_) { - stage.clear(); - } - stages_.clear(); - - // 上流origin情報をリセット - upstreamOriginXSet_ = false; - sourceOriginY_ = 0; - sourceHeight_ = 0; - - // getDataRangeキャッシュをリセット - rangeCache_.origin = {INT32_MIN, INT32_MIN}; - rangeCache_.startX = 0; - rangeCache_.endX = 0; +void VerticalBlurNode::finalize() +{ + // パイプラインステージをクリア + for (auto &stage : stages_) { + stage.clear(); + } + stages_.clear(); + + // 上流origin情報をリセット + upstreamOriginXSet_ = false; + sourceOriginY_ = 0; + sourceHeight_ = 0; + + // getDataRangeキャッシュをリセット + rangeCache_.origin = {INT32_MIN, INT32_MIN}; + rangeCache_.startX = 0; + rangeCache_.endX = 0; } // ======================================== // getDataRange 実装 // ======================================== -DataRange VerticalBlurNode::getDataRange(const RenderRequest &request) const { - Node *upstream = upstreamNode(0); - if (!upstream) { - return DataRange(); - } - - // radius=0の場合は上流をそのまま返す - if (radius_ == 0 || passes_ == 0) { - return upstream->getDataRange(request); - } - - // キャッシュチェック - if (rangeCache_.origin.x == request.origin.x && - rangeCache_.origin.y == request.origin.y) { - if (rangeCache_.startX >= rangeCache_.endX) { - return DataRange{0, 0}; - } - return DataRange{rangeCache_.startX, rangeCache_.endX}; - } - - // 垂直ブラーでは、出力行Yに対して入力行 Y-expansion から Y+expansion の - // X範囲の和集合が必要(expansion = radius * passes) - // 特にアフィン変換された画像では、各行のX範囲が異なる可能性がある - int_fast16_t expansion = radius_ * passes_; - int16_t startX = INT16_MAX; - int16_t endX = INT16_MIN; - - // ブラーカーネル範囲内の全行のX範囲の和集合を計算 - RenderRequest rowRequest = request; - int_fixed baseY = request.origin.y; - for (auto dy = static_cast(-expansion); dy <= expansion; ++dy) { - rowRequest.origin.y = baseY + to_fixed(dy); - DataRange rowRange = upstream->getDataRange(rowRequest); - if (rowRange.hasData()) { - if (rowRange.startX < startX) - startX = rowRange.startX; - if (rowRange.endX > endX) - endX = rowRange.endX; - } - } - - // キャッシュに保存 - rangeCache_.origin = request.origin; - rangeCache_.startX = startX; - rangeCache_.endX = endX; - - if (startX >= endX) { - return DataRange{0, 0}; - } - return DataRange{startX, endX}; +DataRange VerticalBlurNode::getDataRange(const RenderRequest &request) const +{ + Node *upstream = upstreamNode(0); + if (!upstream) { + return DataRange(); + } + + // radius=0の場合は上流をそのまま返す + if (radius_ == 0 || passes_ == 0) { + return upstream->getDataRange(request); + } + + // キャッシュチェック + if (rangeCache_.origin.x == request.origin.x && rangeCache_.origin.y == request.origin.y) { + if (rangeCache_.startX >= rangeCache_.endX) { + return DataRange{0, 0}; + } + return DataRange{rangeCache_.startX, rangeCache_.endX}; + } + + // 垂直ブラーでは、出力行Yに対して入力行 Y-expansion から Y+expansion の + // X範囲の和集合が必要(expansion = radius * passes) + // 特にアフィン変換された画像では、各行のX範囲が異なる可能性がある + int_fast16_t expansion = radius_ * passes_; + int16_t startX = INT16_MAX; + int16_t endX = INT16_MIN; + + // ブラーカーネル範囲内の全行のX範囲の和集合を計算 + RenderRequest rowRequest = request; + int_fixed baseY = request.origin.y; + for (auto dy = static_cast(-expansion); dy <= expansion; ++dy) { + rowRequest.origin.y = baseY + to_fixed(dy); + DataRange rowRange = upstream->getDataRange(rowRequest); + if (rowRange.hasData()) { + if (rowRange.startX < startX) startX = rowRange.startX; + if (rowRange.endX > endX) endX = rowRange.endX; + } + } + + // キャッシュに保存 + rangeCache_.origin = request.origin; + rangeCache_.startX = startX; + rangeCache_.endX = endX; + + if (startX >= endX) { + return DataRange{0, 0}; + } + return DataRange{startX, endX}; } // ======================================== // Template Method フック // ======================================== -PrepareResponse VerticalBlurNode::onPullPrepare(const PrepareRequest &request) { - // 上流へ伝播 - Node *upstream = upstreamNode(0); - if (!upstream) { - // 上流なし: サイズ0を返す - PrepareResponse result; - result.status = PrepareStatus::Prepared; - return result; - } - - PrepareResponse upstreamResult = upstream->pullPrepare(request); - if (!upstreamResult.ok()) { - return upstreamResult; - } +PrepareResponse VerticalBlurNode::onPullPrepare(const PrepareRequest &request) +{ + // 上流へ伝播 + Node *upstream = upstreamNode(0); + if (!upstream) { + // 上流なし: サイズ0を返す + PrepareResponse result; + result.status = PrepareStatus::Prepared; + return result; + } - // スクリーン情報を保存(prepare()代わり) - screenWidth_ = request.width; - screenHeight_ = request.height; - screenOrigin_ = request.origin; + PrepareResponse upstreamResult = upstream->pullPrepare(request); + if (!upstreamResult.ok()) { + return upstreamResult; + } - // 上流のY範囲を保存(getDataRangeでのクエリY座標クランプ用) - // radius=0でも保存しておく(getDataRangeで使用) - sourceOriginY_ = upstreamResult.origin.y; - sourceHeight_ = upstreamResult.height; + // スクリーン情報を保存(prepare()代わり) + screenWidth_ = request.width; + screenHeight_ = request.height; + screenOrigin_ = request.origin; - // radius=0の場合はパススルー(キャッシュ不要) - if (radius_ == 0 || passes_ == 0) { - return upstreamResult; - } + // 上流のY範囲を保存(getDataRangeでのクエリY座標クランプ用) + // radius=0でも保存しておく(getDataRangeで使用) + sourceOriginY_ = upstreamResult.origin.y; + sourceHeight_ = upstreamResult.height; + + // radius=0の場合はパススルー(キャッシュ不要) + if (radius_ == 0 || passes_ == 0) { + return upstreamResult; + } - // 上流AABBに基づいてキャッシュを初期化 - cacheOriginX_ = upstreamResult.origin.x; - initializeStages(upstreamResult.width); + // 上流AABBに基づいてキャッシュを初期化 + cacheOriginX_ = upstreamResult.origin.x; + initializeStages(upstreamResult.width); #ifdef FLEXIMG_DEBUG_PERF_METRICS - // パイプライン方式: 各ステージ (radius*2+1)*width*4 + width*16 - size_t cacheBytes = static_cast(passes_) * - (static_cast(kernelSize()) * - static_cast(cacheWidth_) * 4 + - static_cast(cacheWidth_) * 4 * sizeof(uint32_t)); - PerfMetrics::instance().nodes[NodeType::VerticalBlur].recordAlloc( - cacheBytes, cacheWidth_, kernelSize() * passes_); + // パイプライン方式: 各ステージ (radius*2+1)*width*4 + width*16 + size_t cacheBytes = + static_cast(passes_) * (static_cast(kernelSize()) * static_cast(cacheWidth_) * 4 + + static_cast(cacheWidth_) * 4 * sizeof(uint32_t)); + PerfMetrics::instance().nodes[NodeType::VerticalBlur].recordAlloc(cacheBytes, cacheWidth_, kernelSize() * passes_); #endif - // 垂直ぼかしはY方向に radius * passes 分拡張する - // AABBの高さを拡張し、originのYをシフト(上方向に拡大) - int_fast16_t expansion = radius_ * passes_; - upstreamResult.height = - static_cast(upstreamResult.height + expansion * 2); - upstreamResult.origin.y = upstreamResult.origin.y - to_fixed(expansion); + // 垂直ぼかしはY方向に radius * passes 分拡張する + // AABBの高さを拡張し、originのYをシフト(上方向に拡大) + int_fast16_t expansion = radius_ * passes_; + upstreamResult.height = static_cast(upstreamResult.height + expansion * 2); + upstreamResult.origin.y = upstreamResult.origin.y - to_fixed(expansion); - return upstreamResult; -} - -PrepareResponse VerticalBlurNode::onPushPrepare(const PrepareRequest &request) { - // 下流へ先に伝播してサイズ情報を取得 - Node *downstream = downstreamNode(0); - PrepareResponse downstreamResult; - if (downstream) { - downstreamResult = downstream->pushPrepare(request); - if (!downstreamResult.ok()) { - return downstreamResult; - } - } else { - // 下流なし: 有効なデータがないのでサイズ0を返す - downstreamResult.status = PrepareStatus::Prepared; - // width/height/originはデフォルト値(0)のまま - return downstreamResult; - } - - // radius=0の場合はスルー(キャッシュ初期化不要) - if (radius_ == 0) { - return downstreamResult; - } - - // push用状態を初期化(下流から取得したサイズを使用) - pushInputY_ = 0; - pushOutputY_ = 0; - pushInputWidth_ = downstreamResult.width; - pushInputHeight_ = downstreamResult.height; - // 出力高さ = 入力高さ(push型ではサイズを変えない、エッジはゼロパディング) - pushOutputHeight_ = pushInputHeight_; - baseOriginX_ = downstreamResult.origin.x; // 基準origin.x - pushInputOriginY_ = downstreamResult.origin.y; - lastInputOriginY_ = downstreamResult.origin.y; - - // パイプライン方式でキャッシュを初期化(passes=1でもstages_[0]を使用) - initializeStages(pushInputWidth_); - // 各ステージのpush状態をリセット - for (auto &stage : stages_) { - stage.pushInputY = 0; - stage.pushOutputY = 0; - } - - return downstreamResult; + return upstreamResult; } -void VerticalBlurNode::onPushProcess(RenderResponse &input, - const RenderRequest &request) { - // radius=0の場合はスルー - if (radius_ == 0) { +PrepareResponse VerticalBlurNode::onPushPrepare(const PrepareRequest &request) +{ + // 下流へ先に伝播してサイズ情報を取得 Node *downstream = downstreamNode(0); + PrepareResponse downstreamResult; if (downstream) { - downstream->pushProcess(input, request); + downstreamResult = downstream->pushPrepare(request); + if (!downstreamResult.ok()) { + return downstreamResult; + } + } else { + // 下流なし: 有効なデータがないのでサイズ0を返す + downstreamResult.status = PrepareStatus::Prepared; + // width/height/originはデフォルト値(0)のまま + return downstreamResult; } - return; - } - // パイプライン方式で処理(passes=1でもstages_[0]を使用) - Point inputOrigin = input.origin; - int_fast16_t ks = kernelSize(); - - // Stage 0に入力行を格納 - BlurStage &stage0 = stages_[0]; - int_fast16_t slot0 = static_cast(stage0.pushInputY % ks); + // radius=0の場合はスルー(キャッシュ初期化不要) + if (radius_ == 0) { + return downstreamResult; + } - // 古い行を列合計から減算 - if (stage0.pushInputY >= ks) { - updateStageColSum(stage0, slot0, false); - } + // push用状態を初期化(下流から取得したサイズを使用) + pushInputY_ = 0; + pushOutputY_ = 0; + pushInputWidth_ = downstreamResult.width; + pushInputHeight_ = downstreamResult.height; + // 出力高さ = 入力高さ(push型ではサイズを変えない、エッジはゼロパディング) + pushOutputHeight_ = pushInputHeight_; + baseOriginX_ = downstreamResult.origin.x; // 基準origin.x + pushInputOriginY_ = downstreamResult.origin.y; + lastInputOriginY_ = downstreamResult.origin.y; + + // パイプライン方式でキャッシュを初期化(passes=1でもstages_[0]を使用) + initializeStages(pushInputWidth_); + // 各ステージのpush状態をリセット + for (auto &stage : stages_) { + stage.pushInputY = 0; + stage.pushOutputY = 0; + } - if (!input.isValid()) { - std::memset(stage0.rowCache[static_cast(slot0)].view().data, 0, - static_cast(cacheWidth_) * 4); - } else { - // バッファ準備 - consolidateIfNeeded(input); - inputOrigin = input.origin; // consolidate後のoriginを反映 - ImageBuffer converted = convertFormat(ImageBuffer(input.buffer()), - PixelFormatIDs::RGBA8_Straight); - int_fast16_t xOffset = - static_cast(from_fixed(inputOrigin.x - baseOriginX_)); - storeInputRowToStageCache(stage0, converted, slot0, xOffset); - } - stage0.rowOriginX[static_cast(slot0)] = inputOrigin.x; - - // 新しい行を列合計に加算 - updateStageColSum(stage0, slot0, true); - - lastInputOriginY_ = inputOrigin.y; - stage0.pushInputY++; - - // Stage 0がradius行蓄積後、後続ステージにデータを伝播 - if (stage0.pushInputY > radius_) { - propagatePipelineStages(); - } + return downstreamResult; } -void VerticalBlurNode::onPushFinalize() { - // radius=0の場合はデフォルト動作 - if (radius_ == 0) { - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushFinalize(); +void VerticalBlurNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + // radius=0の場合はスルー + if (radius_ == 0) { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(input, request); + } + return; } - finalize(); - return; - } - // パイプライン方式で残りの行を出力(passes=1でもstages_[0]を使用) - int_fast16_t ks = kernelSize(); + // パイプライン方式で処理(passes=1でもstages_[0]を使用) + Point inputOrigin = input.origin; + int_fast16_t ks = kernelSize(); - // 残りの行を出力(下端はゼロパディング扱い) - while (pushOutputY_ < pushOutputHeight_) { - // Stage 0にゼロ行を追加 - BlurStage &stage0 = stages_[0]; + // Stage 0に入力行を格納 + BlurStage &stage0 = stages_[0]; int_fast16_t slot0 = static_cast(stage0.pushInputY % ks); + // 古い行を列合計から減算 if (stage0.pushInputY >= ks) { - updateStageColSum(stage0, slot0, false); + updateStageColSum(stage0, slot0, false); + } + + if (!input.isValid()) { + std::memset(stage0.rowCache[static_cast(slot0)].view().data, 0, static_cast(cacheWidth_) * 4); + } else { + // バッファ準備 + consolidateIfNeeded(input); + inputOrigin = input.origin; // consolidate後のoriginを反映 + ImageBuffer converted = convertFormat(ImageBuffer(input.buffer()), PixelFormatIDs::RGBA8_Straight); + int_fast16_t xOffset = static_cast(from_fixed(inputOrigin.x - baseOriginX_)); + storeInputRowToStageCache(stage0, converted, slot0, xOffset); } - std::memset(stage0.rowCache[static_cast(slot0)].view().data, 0, - static_cast(cacheWidth_) * 4); + stage0.rowOriginX[static_cast(slot0)] = inputOrigin.x; + + // 新しい行を列合計に加算 + updateStageColSum(stage0, slot0, true); - // パディング行は画像の下端より下なのでorigin.yは増加する(新座標系) - lastInputOriginY_ += to_fixed(1); + lastInputOriginY_ = inputOrigin.y; stage0.pushInputY++; - // 後続ステージに伝播 - propagatePipelineStages(); - } + // Stage 0がradius行蓄積後、後続ステージにデータを伝播 + if (stage0.pushInputY > radius_) { + propagatePipelineStages(); + } +} + +void VerticalBlurNode::onPushFinalize() +{ + // radius=0の場合はデフォルト動作 + if (radius_ == 0) { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushFinalize(); + } + finalize(); + return; + } + + // パイプライン方式で残りの行を出力(passes=1でもstages_[0]を使用) + int_fast16_t ks = kernelSize(); - // デフォルト動作: 下流へ伝播し、finalize()を呼び出す - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushFinalize(); - } - finalize(); + // 残りの行を出力(下端はゼロパディング扱い) + while (pushOutputY_ < pushOutputHeight_) { + // Stage 0にゼロ行を追加 + BlurStage &stage0 = stages_[0]; + int_fast16_t slot0 = static_cast(stage0.pushInputY % ks); + + if (stage0.pushInputY >= ks) { + updateStageColSum(stage0, slot0, false); + } + std::memset(stage0.rowCache[static_cast(slot0)].view().data, 0, static_cast(cacheWidth_) * 4); + + // パディング行は画像の下端より下なのでorigin.yは増加する(新座標系) + lastInputOriginY_ += to_fixed(1); + stage0.pushInputY++; + + // 後続ステージに伝播 + propagatePipelineStages(); + } + + // デフォルト動作: 下流へ伝播し、finalize()を呼び出す + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushFinalize(); + } + finalize(); } -RenderResponse &VerticalBlurNode::onPullProcess(const RenderRequest &request) { - Node *upstream = upstreamNode(0); - if (!upstream) - return makeEmptyResponse(request.origin); +RenderResponse &VerticalBlurNode::onPullProcess(const RenderRequest &request) +{ + Node *upstream = upstreamNode(0); + if (!upstream) return makeEmptyResponse(request.origin); - // radius=0の場合は処理をスキップしてスルー出力 - if (radius_ == 0) { - return upstream->pullProcess(request); - } + // radius=0の場合は処理をスキップしてスルー出力 + if (radius_ == 0) { + return upstream->pullProcess(request); + } - // パイプライン方式で処理(passes=1でもstages_[0]を使用) - return pullProcessPipeline(upstream, request); + // パイプライン方式で処理(passes=1でもstages_[0]を使用) + return pullProcessPipeline(upstream, request); } // ======================================== // パイプライン処理 // ======================================== -RenderResponse & -VerticalBlurNode::pullProcessPipeline(Node *upstream, - const RenderRequest &request) { - int_fast16_t requestY = - static_cast(from_fixed(request.origin.y)); - // 注: 各ステージの初期化はupdateStageCache内で行われる - - // 最終ステージのキャッシュを更新(再帰的に前段ステージも更新される) - // updateStageCache内で上流をpullするため、計測はこの後から開始 - updateStageCache(passes_ - 1, upstream, request, requestY); - - // 有効範囲を取得(キャッシュがあれば再利用) - DataRange range; - if (rangeCache_.origin.x == request.origin.x && - rangeCache_.origin.y == request.origin.y) { - range = DataRange{rangeCache_.startX, rangeCache_.endX}; - } else { - range = getDataRange(request); - } - - // 有効なデータがない場合は空を返す(originは維持) - if (!range.hasData()) { - return makeEmptyResponse(request.origin); - } - - FLEXIMG_METRICS_SCOPE(NodeType::VerticalBlur); - - (void)range; // 未使用(独自に交差領域を計算) - - // SourceNodeと同じ交差領域計算を行う - // upstreamOriginX_は「キャッシュ左端のワールド座標」 - int_fixed cacheLeft = upstreamOriginX_; // キャッシュ左端のワールド座標 - int_fixed cacheRight = cacheLeft + to_fixed(cacheWidth_); // キャッシュ右端 - int_fixed reqLeft = request.origin.x; // リクエスト左端のワールド座標 - int_fixed reqRight = reqLeft + to_fixed(request.width); // リクエスト右端 - - // 交差領域 - int_fixed interLeft = std::max(cacheLeft, reqLeft); - int_fixed interRight = std::min(cacheRight, reqRight); - - // 交差領域がなければ空を返す(originは維持) - if (interLeft >= interRight) { - return makeEmptyResponse(request.origin); - } - - // キャッシュ内のオフセットと出力幅を計算(SourceNodeと同じ丸め方式) - int_fast16_t srcStartX = - static_cast(from_fixed_floor(interLeft - cacheLeft)); - int_fast16_t srcEndX = - static_cast(from_fixed_ceil(interRight - cacheLeft)); - int16_t outputWidth = static_cast(srcEndX - srcStartX); +RenderResponse &VerticalBlurNode::pullProcessPipeline(Node *upstream, const RenderRequest &request) +{ + int_fast16_t requestY = static_cast(from_fixed(request.origin.y)); + // 注: 各ステージの初期化はupdateStageCache内で行われる + + // 最終ステージのキャッシュを更新(再帰的に前段ステージも更新される) + // updateStageCache内で上流をpullするため、計測はこの後から開始 + updateStageCache(passes_ - 1, upstream, request, requestY); + + // 有効範囲を取得(キャッシュがあれば再利用) + DataRange range; + if (rangeCache_.origin.x == request.origin.x && rangeCache_.origin.y == request.origin.y) { + range = DataRange{rangeCache_.startX, rangeCache_.endX}; + } else { + range = getDataRange(request); + } + + // 有効なデータがない場合は空を返す(originは維持) + if (!range.hasData()) { + return makeEmptyResponse(request.origin); + } + + FLEXIMG_METRICS_SCOPE(NodeType::VerticalBlur); + + (void)range; // 未使用(独自に交差領域を計算) + + // SourceNodeと同じ交差領域計算を行う + // upstreamOriginX_は「キャッシュ左端のワールド座標」 + int_fixed cacheLeft = upstreamOriginX_; // キャッシュ左端のワールド座標 + int_fixed cacheRight = cacheLeft + to_fixed(cacheWidth_); // キャッシュ右端 + int_fixed reqLeft = request.origin.x; // リクエスト左端のワールド座標 + int_fixed reqRight = reqLeft + to_fixed(request.width); // リクエスト右端 + + // 交差領域 + int_fixed interLeft = std::max(cacheLeft, reqLeft); + int_fixed interRight = std::min(cacheRight, reqRight); + + // 交差領域がなければ空を返す(originは維持) + if (interLeft >= interRight) { + return makeEmptyResponse(request.origin); + } + + // キャッシュ内のオフセットと出力幅を計算(SourceNodeと同じ丸め方式) + int_fast16_t srcStartX = static_cast(from_fixed_floor(interLeft - cacheLeft)); + int_fast16_t srcEndX = static_cast(from_fixed_ceil(interRight - cacheLeft)); + int16_t outputWidth = static_cast(srcEndX - srcStartX); #ifdef FLEXIMG_DEBUG_PERF_METRICS - auto &metrics = PerfMetrics::instance().nodes[NodeType::VerticalBlur]; - metrics.requestedPixels += static_cast(request.width) * 1; - metrics.usedPixels += static_cast(outputWidth) * 1; + auto &metrics = PerfMetrics::instance().nodes[NodeType::VerticalBlur]; + metrics.requestedPixels += static_cast(request.width) * 1; + metrics.usedPixels += static_cast(outputWidth) * 1; #endif - ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Uninitialized); + ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); #ifdef FLEXIMG_DEBUG_PERF_METRICS - metrics.recordAlloc(output.totalBytes(), output.width(), output.height()); + metrics.recordAlloc(output.totalBytes(), output.width(), output.height()); #endif - // 最終ステージの列合計から出力行を計算(有効範囲のみ) - BlurStage &lastStage = stages_[static_cast(passes_ - 1)]; - uint8_t *outRow = static_cast(output.view().data); - int_fast16_t ks = kernelSize(); - - for (int_fast16_t cacheX = srcStartX; cacheX < srcEndX; cacheX++) { - size_t outOff = static_cast(cacheX - srcStartX) * 4; - - if (lastStage.colSumA[static_cast(cacheX)] > 0) { - size_t cx = static_cast(cacheX); - outRow[outOff] = - static_cast(lastStage.colSumR[cx] / lastStage.colSumA[cx]); - outRow[outOff + 1] = - static_cast(lastStage.colSumG[cx] / lastStage.colSumA[cx]); - outRow[outOff + 2] = - static_cast(lastStage.colSumB[cx] / lastStage.colSumA[cx]); - outRow[outOff + 3] = static_cast(lastStage.colSumA[cx] / - static_cast(ks)); - } else { - outRow[outOff] = outRow[outOff + 1] = outRow[outOff + 2] = - outRow[outOff + 3] = 0; + // 最終ステージの列合計から出力行を計算(有効範囲のみ) + BlurStage &lastStage = stages_[static_cast(passes_ - 1)]; + uint8_t *outRow = static_cast(output.view().data); + int_fast16_t ks = kernelSize(); + + for (int_fast16_t cacheX = srcStartX; cacheX < srcEndX; cacheX++) { + size_t outOff = static_cast(cacheX - srcStartX) * 4; + + if (lastStage.colSumA[static_cast(cacheX)] > 0) { + size_t cx = static_cast(cacheX); + outRow[outOff] = static_cast(lastStage.colSumR[cx] / lastStage.colSumA[cx]); + outRow[outOff + 1] = static_cast(lastStage.colSumG[cx] / lastStage.colSumA[cx]); + outRow[outOff + 2] = static_cast(lastStage.colSumB[cx] / lastStage.colSumA[cx]); + outRow[outOff + 3] = static_cast(lastStage.colSumA[cx] / static_cast(ks)); + } else { + outRow[outOff] = outRow[outOff + 1] = outRow[outOff + 2] = outRow[outOff + 3] = 0; + } } - } - // 出力の origin を計算(バッファ左上のワールド座標) - Point outputOrigin; - outputOrigin.x = interLeft; - outputOrigin.y = request.origin.y; + // 出力の origin を計算(バッファ左上のワールド座標) + Point outputOrigin; + outputOrigin.x = interLeft; + outputOrigin.y = request.origin.y; - return makeResponse(std::move(output), outputOrigin); + return makeResponse(std::move(output), outputOrigin); } -void VerticalBlurNode::updateStageCache(int_fast16_t stageIndex, Node *upstream, - const RenderRequest &request, - int_fast16_t newY) { - BlurStage &stage = stages_[static_cast(stageIndex)]; - int_fast16_t ks = kernelSize(); +void VerticalBlurNode::updateStageCache(int_fast16_t stageIndex, Node *upstream, const RenderRequest &request, + int_fast16_t newY) +{ + BlurStage &stage = stages_[static_cast(stageIndex)]; + int_fast16_t ks = kernelSize(); + + // このステージへの最初の呼び出し時、currentYを調整してキャッシュを完全に充填 + // newY - kernelSize() + // から開始することで、kernelSize()回のループでキャッシュが充填される + if (!stage.cacheReady) { + stage.currentY = newY - ks; + stage.cacheReady = true; + } - // このステージへの最初の呼び出し時、currentYを調整してキャッシュを完全に充填 - // newY - kernelSize() - // から開始することで、kernelSize()回のループでキャッシュが充填される - if (!stage.cacheReady) { - stage.currentY = newY - ks; - stage.cacheReady = true; - } + if (stage.currentY == newY) return; - if (stage.currentY == newY) - return; + int_fast16_t step = (stage.currentY < newY) ? 1 : -1; - int_fast16_t step = (stage.currentY < newY) ? 1 : -1; + while (stage.currentY != newY) { + int_fast16_t newSrcY = static_cast(stage.currentY + step * (radius_ + 1)); + int_fast16_t slot = static_cast(newSrcY % ks); + if (slot < 0) slot += ks; - while (stage.currentY != newY) { - int_fast16_t newSrcY = - static_cast(stage.currentY + step * (radius_ + 1)); - int_fast16_t slot = static_cast(newSrcY % ks); - if (slot < 0) - slot += ks; + // 古い行を列合計から減算 + updateStageColSum(stage, slot, false); - // 古い行を列合計から減算 - updateStageColSum(stage, slot, false); + // 新しい行を取得してキャッシュに格納 + if (stageIndex == 0) { + // Stage 0: 上流から直接取得 + fetchRowToStageCache(stage, upstream, request, newSrcY, slot); + } else { + // Stage 1以降: 前段ステージから取得 + fetchRowFromPrevStage(stageIndex, upstream, request, newSrcY, slot); + } - // 新しい行を取得してキャッシュに格納 - if (stageIndex == 0) { - // Stage 0: 上流から直接取得 - fetchRowToStageCache(stage, upstream, request, newSrcY, slot); - } else { - // Stage 1以降: 前段ステージから取得 - fetchRowFromPrevStage(stageIndex, upstream, request, newSrcY, slot); + // 新しい行を列合計に加算 + updateStageColSum(stage, slot, true); + + stage.currentY += step; } +} - // 新しい行を列合計に加算 - updateStageColSum(stage, slot, true); +void VerticalBlurNode::fetchRowToStageCache(BlurStage &stage, Node *upstream, const RenderRequest &request, + int_fast16_t srcY, int_fast16_t cacheIndex) +{ + // キャッシュ幅・原点を使用してリクエスト作成 + RenderRequest upstreamReq; + upstreamReq.width = static_cast(cacheWidth_); + upstreamReq.height = 1; + upstreamReq.origin.x = cacheOriginX_; + upstreamReq.origin.y = to_fixed(srcY); + + // 上流のデータ範囲を取得して記録 + DataRange dataRange = upstream->getDataRange(upstreamReq); + stage.rowDataRange[static_cast(cacheIndex)] = dataRange; + + // キャッシュ行をゼロクリア + ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); + std::memset(dstView.data, 0, static_cast(cacheWidth_) * 4); + + if (!dataRange.hasData()) { + return; + } - stage.currentY += step; - } -} + RenderResponse &result = upstream->pullProcess(upstreamReq); + if (!result.isValid()) { + return; + } + + // バッファ準備 + consolidateIfNeeded(result); + + // upstreamOriginX_はpullProcessPipelineで出力のorigin.x計算に使用 + // アフィン変換された場合、各行のorigin.xが異なる可能性があるため、 + // AABBのorigin.x(cacheOriginX_、onPullPrepareで設定済み)を使用する + if (!upstreamOriginXSet_) { + upstreamOriginX_ = cacheOriginX_; + upstreamOriginXSet_ = true; + } + + ImageBuffer converted = convertFormat(ImageBuffer(result.buffer()), PixelFormatIDs::RGBA8_Straight); + ViewPort srcView = converted.view(); + + // 入力データをキャッシュにコピー(オフセット考慮) + // cacheOriginX_(更新済み)を使用して正しい座標でコピーする + // result.origin.x - cacheOriginX_ = 入力バッファ左端 - キャッシュ左端 + int_fast16_t srcOffsetX = static_cast(from_fixed(result.origin.x - cacheOriginX_)); + int_fast16_t dstStartX = std::max(0, srcOffsetX); + int_fast16_t srcStartX = std::max(0, -srcOffsetX); + int_fast16_t copyWidth = + std::min(static_cast(srcView.width) - srcStartX, cacheWidth_ - dstStartX); + if (copyWidth > 0) { + const uint8_t *srcPtr = static_cast(srcView.data) + srcStartX * 4; + std::memcpy(static_cast(dstView.data) + dstStartX * 4, srcPtr, static_cast(copyWidth) * 4); + } -void VerticalBlurNode::fetchRowToStageCache(BlurStage &stage, Node *upstream, - const RenderRequest &request, - int_fast16_t srcY, - int_fast16_t cacheIndex) { - // キャッシュ幅・原点を使用してリクエスト作成 - RenderRequest upstreamReq; - upstreamReq.width = static_cast(cacheWidth_); - upstreamReq.height = 1; - upstreamReq.origin.x = cacheOriginX_; - upstreamReq.origin.y = to_fixed(srcY); - - // 上流のデータ範囲を取得して記録 - DataRange dataRange = upstream->getDataRange(upstreamReq); - stage.rowDataRange[static_cast(cacheIndex)] = dataRange; - - // キャッシュ行をゼロクリア - ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); - std::memset(dstView.data, 0, static_cast(cacheWidth_) * 4); - - if (!dataRange.hasData()) { - return; - } - - RenderResponse &result = upstream->pullProcess(upstreamReq); - if (!result.isValid()) { - return; - } - - // バッファ準備 - consolidateIfNeeded(result); - - // upstreamOriginX_はpullProcessPipelineで出力のorigin.x計算に使用 - // アフィン変換された場合、各行のorigin.xが異なる可能性があるため、 - // AABBのorigin.x(cacheOriginX_、onPullPrepareで設定済み)を使用する - if (!upstreamOriginXSet_) { - upstreamOriginX_ = cacheOriginX_; - upstreamOriginXSet_ = true; - } - - ImageBuffer converted = convertFormat(ImageBuffer(result.buffer()), - PixelFormatIDs::RGBA8_Straight); - ViewPort srcView = converted.view(); - - // 入力データをキャッシュにコピー(オフセット考慮) - // cacheOriginX_(更新済み)を使用して正しい座標でコピーする - // result.origin.x - cacheOriginX_ = 入力バッファ左端 - キャッシュ左端 - int_fast16_t srcOffsetX = - static_cast(from_fixed(result.origin.x - cacheOriginX_)); - int_fast16_t dstStartX = std::max(0, srcOffsetX); - int_fast16_t srcStartX = std::max(0, -srcOffsetX); - int_fast16_t copyWidth = std::min( - static_cast(srcView.width) - srcStartX, - cacheWidth_ - dstStartX); - if (copyWidth > 0) { - const uint8_t *srcPtr = - static_cast(srcView.data) + srcStartX * 4; - std::memcpy(static_cast(dstView.data) + dstStartX * 4, srcPtr, - static_cast(copyWidth) * 4); - } - - (void)request; // 現在は未使用(将来の拡張用) + (void)request; // 現在は未使用(将来の拡張用) } -void VerticalBlurNode::fetchRowFromPrevStage(int_fast16_t stageIndex, - Node *upstream, - const RenderRequest &request, - int_fast16_t srcY, - int_fast16_t cacheIndex) { - BlurStage &stage = stages_[static_cast(stageIndex)]; - BlurStage &prevStage = stages_[static_cast(stageIndex - 1)]; - - // 前段ステージのキャッシュを更新 - updateStageCache(stageIndex - 1, upstream, request, srcY); - - // 前段ステージの列合計から1行を計算してキャッシュに格納 - ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); - uint8_t *dstRow = static_cast(dstView.data); - - // 有効範囲を追跡 - int16_t startX = static_cast(cacheWidth_); - int16_t endX = 0; - - int_fast16_t ks = kernelSize(); - for (size_t x = 0; x < static_cast(cacheWidth_); x++) { - size_t off = x * 4; - if (prevStage.colSumA[x] > 0) { - dstRow[off] = - static_cast(prevStage.colSumR[x] / prevStage.colSumA[x]); - dstRow[off + 1] = - static_cast(prevStage.colSumG[x] / prevStage.colSumA[x]); - dstRow[off + 2] = - static_cast(prevStage.colSumB[x] / prevStage.colSumA[x]); - dstRow[off + 3] = static_cast(prevStage.colSumA[x] / - static_cast(ks)); - // 有効範囲を更新 - if (static_cast(x) < startX) - startX = static_cast(x); - endX = static_cast(x + 1); - } else { - dstRow[off] = dstRow[off + 1] = dstRow[off + 2] = dstRow[off + 3] = 0; +void VerticalBlurNode::fetchRowFromPrevStage(int_fast16_t stageIndex, Node *upstream, const RenderRequest &request, + int_fast16_t srcY, int_fast16_t cacheIndex) +{ + BlurStage &stage = stages_[static_cast(stageIndex)]; + BlurStage &prevStage = stages_[static_cast(stageIndex - 1)]; + + // 前段ステージのキャッシュを更新 + updateStageCache(stageIndex - 1, upstream, request, srcY); + + // 前段ステージの列合計から1行を計算してキャッシュに格納 + ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); + uint8_t *dstRow = static_cast(dstView.data); + + // 有効範囲を追跡 + int16_t startX = static_cast(cacheWidth_); + int16_t endX = 0; + + int_fast16_t ks = kernelSize(); + for (size_t x = 0; x < static_cast(cacheWidth_); x++) { + size_t off = x * 4; + if (prevStage.colSumA[x] > 0) { + dstRow[off] = static_cast(prevStage.colSumR[x] / prevStage.colSumA[x]); + dstRow[off + 1] = static_cast(prevStage.colSumG[x] / prevStage.colSumA[x]); + dstRow[off + 2] = static_cast(prevStage.colSumB[x] / prevStage.colSumA[x]); + dstRow[off + 3] = static_cast(prevStage.colSumA[x] / static_cast(ks)); + // 有効範囲を更新 + if (static_cast(x) < startX) startX = static_cast(x); + endX = static_cast(x + 1); + } else { + dstRow[off] = dstRow[off + 1] = dstRow[off + 2] = dstRow[off + 3] = 0; + } } - } - // DataRangeを記録 - stage.rowDataRange[static_cast(cacheIndex)] = DataRange{startX, endX}; + // DataRangeを記録 + stage.rowDataRange[static_cast(cacheIndex)] = DataRange{startX, endX}; } -void VerticalBlurNode::updateStageColSum(BlurStage &stage, - int_fast16_t cacheIndex, bool add) { - const uint8_t *row = static_cast( - stage.rowCache[static_cast(cacheIndex)].view().data); - int_fast16_t sign = add ? 1 : -1; - for (size_t x = 0; x < static_cast(cacheWidth_); x++) { - size_t off = x * 4; - int32_t a = row[off + 3] * sign; - int32_t ra = row[off] * a; - int32_t ga = row[off + 1] * a; - int32_t ba = row[off + 2] * a; - stage.colSumR[x] += static_cast(ra); - stage.colSumG[x] += static_cast(ga); - stage.colSumB[x] += static_cast(ba); - stage.colSumA[x] += static_cast(a); - } +void VerticalBlurNode::updateStageColSum(BlurStage &stage, int_fast16_t cacheIndex, bool add) +{ + const uint8_t *row = static_cast(stage.rowCache[static_cast(cacheIndex)].view().data); + int_fast16_t sign = add ? 1 : -1; + for (size_t x = 0; x < static_cast(cacheWidth_); x++) { + size_t off = x * 4; + int32_t a = row[off + 3] * sign; + int32_t ra = row[off] * a; + int32_t ga = row[off + 1] * a; + int32_t ba = row[off + 2] * a; + stage.colSumR[x] += static_cast(ra); + stage.colSumG[x] += static_cast(ga); + stage.colSumB[x] += static_cast(ba); + stage.colSumA[x] += static_cast(a); + } } -void VerticalBlurNode::computeStageOutputRow(BlurStage &stage, - ImageBuffer &output, - int_fast16_t width) { - uint8_t *outRow = static_cast(output.view().data); - int_fast16_t ks = kernelSize(); - for (size_t x = 0; x < static_cast(width); x++) { - size_t off = x * 4; - if (stage.colSumA[x] > 0) { - outRow[off] = static_cast(stage.colSumR[x] / stage.colSumA[x]); - outRow[off + 1] = - static_cast(stage.colSumG[x] / stage.colSumA[x]); - outRow[off + 2] = - static_cast(stage.colSumB[x] / stage.colSumA[x]); - outRow[off + 3] = - static_cast(stage.colSumA[x] / static_cast(ks)); - } else { - outRow[off] = outRow[off + 1] = outRow[off + 2] = outRow[off + 3] = 0; +void VerticalBlurNode::computeStageOutputRow(BlurStage &stage, ImageBuffer &output, int_fast16_t width) +{ + uint8_t *outRow = static_cast(output.view().data); + int_fast16_t ks = kernelSize(); + for (size_t x = 0; x < static_cast(width); x++) { + size_t off = x * 4; + if (stage.colSumA[x] > 0) { + outRow[off] = static_cast(stage.colSumR[x] / stage.colSumA[x]); + outRow[off + 1] = static_cast(stage.colSumG[x] / stage.colSumA[x]); + outRow[off + 2] = static_cast(stage.colSumB[x] / stage.colSumA[x]); + outRow[off + 3] = static_cast(stage.colSumA[x] / static_cast(ks)); + } else { + outRow[off] = outRow[off + 1] = outRow[off + 2] = outRow[off + 3] = 0; + } } - } } // ======================================== // キャッシュ管理 // ======================================== -void VerticalBlurNode::initializeStage(BlurStage &stage, int_fast16_t width) { - size_t cacheRows = static_cast(kernelSize()); // radius*2+1 - stage.rowCache.resize(cacheRows); - stage.rowOriginX.assign(cacheRows, 0); - stage.rowDataRange.assign(cacheRows, DataRange{0, 0}); // 空範囲で初期化 - for (size_t i = 0; i < cacheRows; i++) { - stage.rowCache[i] = ImageBuffer(width, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Zero, allocator()); - } - stage.colSumR.assign(static_cast(width), 0); - stage.colSumG.assign(static_cast(width), 0); - stage.colSumB.assign(static_cast(width), 0); - stage.colSumA.assign(static_cast(width), 0); - stage.currentY = 0; - stage.cacheReady = false; +void VerticalBlurNode::initializeStage(BlurStage &stage, int_fast16_t width) +{ + size_t cacheRows = static_cast(kernelSize()); // radius*2+1 + stage.rowCache.resize(cacheRows); + stage.rowOriginX.assign(cacheRows, 0); + stage.rowDataRange.assign(cacheRows, DataRange{0, 0}); // 空範囲で初期化 + for (size_t i = 0; i < cacheRows; i++) { + stage.rowCache[i] = ImageBuffer(width, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Zero, allocator()); + } + stage.colSumR.assign(static_cast(width), 0); + stage.colSumG.assign(static_cast(width), 0); + stage.colSumB.assign(static_cast(width), 0); + stage.colSumA.assign(static_cast(width), 0); + stage.currentY = 0; + stage.cacheReady = false; } -void VerticalBlurNode::initializeStages(int_fast16_t width) { - cacheWidth_ = static_cast(width); - stages_.resize(static_cast(passes_)); - for (size_t i = 0; i < static_cast(passes_); i++) { - initializeStage(stages_[i], width); - } +void VerticalBlurNode::initializeStages(int_fast16_t width) +{ + cacheWidth_ = static_cast(width); + stages_.resize(static_cast(passes_)); + for (size_t i = 0; i < static_cast(passes_); i++) { + initializeStage(stages_[i], width); + } } // ======================================== // push型用ヘルパー関数 // ======================================== -void VerticalBlurNode::propagatePipelineStages() { - int_fast16_t ks = kernelSize(); - - // Stage 0の出力を計算してStage 1以降に伝播 - for (int_fast16_t s = 1; s < passes_; s++) { - BlurStage &prevStage = stages_[static_cast(s - 1)]; - BlurStage &stage = stages_[static_cast(s)]; - - // 前段ステージの列合計から1行を計算 - ImageBuffer stageInput(cacheWidth_, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Uninitialized); - uint8_t *stageRow = static_cast(stageInput.view().data); - - for (size_t x = 0; x < static_cast(cacheWidth_); x++) { - size_t off = x * 4; - if (prevStage.colSumA[x] > 0) { - stageRow[off] = - static_cast(prevStage.colSumR[x] / prevStage.colSumA[x]); - stageRow[off + 1] = - static_cast(prevStage.colSumG[x] / prevStage.colSumA[x]); - stageRow[off + 2] = - static_cast(prevStage.colSumB[x] / prevStage.colSumA[x]); - stageRow[off + 3] = static_cast(prevStage.colSumA[x] / - static_cast(ks)); - } else { - stageRow[off] = stageRow[off + 1] = stageRow[off + 2] = - stageRow[off + 3] = 0; - } - } - - // 前段の出力行カウントを更新 - prevStage.pushOutputY++; - - // 現段ステージのキャッシュに格納 - int_fast16_t slot = static_cast(stage.pushInputY % ks); - - // 古い行を列合計から減算 - if (stage.pushInputY >= ks) { - updateStageColSum(stage, slot, false); +void VerticalBlurNode::propagatePipelineStages() +{ + int_fast16_t ks = kernelSize(); + + // Stage 0の出力を計算してStage 1以降に伝播 + for (int_fast16_t s = 1; s < passes_; s++) { + BlurStage &prevStage = stages_[static_cast(s - 1)]; + BlurStage &stage = stages_[static_cast(s)]; + + // 前段ステージの列合計から1行を計算 + ImageBuffer stageInput(cacheWidth_, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); + uint8_t *stageRow = static_cast(stageInput.view().data); + + for (size_t x = 0; x < static_cast(cacheWidth_); x++) { + size_t off = x * 4; + if (prevStage.colSumA[x] > 0) { + stageRow[off] = static_cast(prevStage.colSumR[x] / prevStage.colSumA[x]); + stageRow[off + 1] = static_cast(prevStage.colSumG[x] / prevStage.colSumA[x]); + stageRow[off + 2] = static_cast(prevStage.colSumB[x] / prevStage.colSumA[x]); + stageRow[off + 3] = static_cast(prevStage.colSumA[x] / static_cast(ks)); + } else { + stageRow[off] = stageRow[off + 1] = stageRow[off + 2] = stageRow[off + 3] = 0; + } + } + + // 前段の出力行カウントを更新 + prevStage.pushOutputY++; + + // 現段ステージのキャッシュに格納 + int_fast16_t slot = static_cast(stage.pushInputY % ks); + + // 古い行を列合計から減算 + if (stage.pushInputY >= ks) { + updateStageColSum(stage, slot, false); + } + + // 新しい行をキャッシュに格納 + ViewPort srcView = stageInput.view(); + ViewPort dstView = stage.rowCache[static_cast(slot)].view(); + std::memcpy(dstView.data, srcView.data, static_cast(cacheWidth_) * 4); + + // 新しい行を列合計に加算 + updateStageColSum(stage, slot, true); + + stage.pushInputY++; + + // このステージがまだradius行蓄積していない場合は伝播終了 + if (stage.pushInputY <= radius_) { + return; + } } - // 新しい行をキャッシュに格納 - ViewPort srcView = stageInput.view(); - ViewPort dstView = stage.rowCache[static_cast(slot)].view(); - std::memcpy(dstView.data, srcView.data, - static_cast(cacheWidth_) * 4); - - // 新しい行を列合計に加算 - updateStageColSum(stage, slot, true); + // 最終ステージが出力可能になったら下流にpush + emitBlurredLinePipeline(); +} - stage.pushInputY++; +void VerticalBlurNode::emitBlurredLinePipeline() +{ + BlurStage &lastStage = stages_[static_cast(passes_ - 1)]; + int_fast16_t ks = kernelSize(); - // このステージがまだradius行蓄積していない場合は伝播終了 - if (stage.pushInputY <= radius_) { - return; - } - } + ImageBuffer output(cacheWidth_, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); + uint8_t *outRow = static_cast(output.view().data); - // 最終ステージが出力可能になったら下流にpush - emitBlurredLinePipeline(); -} - -void VerticalBlurNode::emitBlurredLinePipeline() { - BlurStage &lastStage = stages_[static_cast(passes_ - 1)]; - int_fast16_t ks = kernelSize(); - - ImageBuffer output(cacheWidth_, 1, PixelFormatIDs::RGBA8_Straight, - InitPolicy::Uninitialized); - uint8_t *outRow = static_cast(output.view().data); - - for (size_t x = 0; x < static_cast(cacheWidth_); x++) { - size_t off = x * 4; - if (lastStage.colSumA[x] > 0) { - outRow[off] = - static_cast(lastStage.colSumR[x] / lastStage.colSumA[x]); - outRow[off + 1] = - static_cast(lastStage.colSumG[x] / lastStage.colSumA[x]); - outRow[off + 2] = - static_cast(lastStage.colSumB[x] / lastStage.colSumA[x]); - outRow[off + 3] = static_cast(lastStage.colSumA[x] / - static_cast(ks)); - } else { - outRow[off] = outRow[off + 1] = outRow[off + 2] = outRow[off + 3] = 0; + for (size_t x = 0; x < static_cast(cacheWidth_); x++) { + size_t off = x * 4; + if (lastStage.colSumA[x] > 0) { + outRow[off] = static_cast(lastStage.colSumR[x] / lastStage.colSumA[x]); + outRow[off + 1] = static_cast(lastStage.colSumG[x] / lastStage.colSumA[x]); + outRow[off + 2] = static_cast(lastStage.colSumB[x] / lastStage.colSumA[x]); + outRow[off + 3] = static_cast(lastStage.colSumA[x] / static_cast(ks)); + } else { + outRow[off] = outRow[off + 1] = outRow[off + 2] = outRow[off + 3] = 0; + } } - } - lastStage.pushOutputY++; + lastStage.pushOutputY++; - // origin計算 - // lastInputOriginY_は最後に受信した入力行のorigin.y - // 出力行のorigin.yは、入力行との差分を減算して求める - int_fixed originX = baseOriginX_; - int32_t rowDiff = (stages_[0].pushInputY - 1) - pushOutputY_; - int_fixed originY = lastInputOriginY_ - to_fixed(rowDiff); + // origin計算 + // lastInputOriginY_は最後に受信した入力行のorigin.y + // 出力行のorigin.yは、入力行との差分を減算して求める + int_fixed originX = baseOriginX_; + int32_t rowDiff = (stages_[0].pushInputY - 1) - pushOutputY_; + int_fixed originY = lastInputOriginY_ - to_fixed(rowDiff); - RenderRequest outReq; - outReq.width = static_cast(cacheWidth_); - outReq.height = 1; - outReq.origin.x = originX; - outReq.origin.y = originY; + RenderRequest outReq; + outReq.width = static_cast(cacheWidth_); + outReq.height = 1; + outReq.origin.x = originX; + outReq.origin.y = originY; - pushOutputY_++; + pushOutputY_++; - Node *downstream = downstreamNode(0); - if (downstream) { - RenderResponse &resp = makeResponse(std::move(output), outReq.origin); - downstream->pushProcess(resp, outReq); - } + Node *downstream = downstreamNode(0); + if (downstream) { + RenderResponse &resp = makeResponse(std::move(output), outReq.origin); + downstream->pushProcess(resp, outReq); + } } -void VerticalBlurNode::storeInputRowToStageCache(BlurStage &stage, - const ImageBuffer &input, - int_fast16_t cacheIndex, - int_fast16_t xOffset) { - ViewPort srcView = input.view(); - ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); - const uint8_t *srcData = static_cast(srcView.data); - uint8_t *dstData = static_cast(dstView.data); - int_fast16_t srcWidth = static_cast(srcView.width); - - // キャッシュをゼロクリア - std::memset(dstData, 0, static_cast(cacheWidth_) * 4); - - // コピー範囲の計算(pull pathのfetchRowToStageCacheと同じロジック) - // xOffset > 0: 入力がキャッシュより右にある → cache[xOffset]に書き込み - // xOffset < 0: 入力がキャッシュより左にある → source[-xOffset]から読み込み - int_fast16_t dstStart = std::max(0, xOffset); - int_fast16_t srcStart = std::max(0, -xOffset); - int_fast16_t copyWidth = - std::min(srcWidth - srcStart, cacheWidth_ - dstStart); - - if (copyWidth > 0) { - std::memcpy(dstData + dstStart * 4, srcData + srcStart * 4, - static_cast(copyWidth) * 4); - } +void VerticalBlurNode::storeInputRowToStageCache(BlurStage &stage, const ImageBuffer &input, int_fast16_t cacheIndex, + int_fast16_t xOffset) +{ + ViewPort srcView = input.view(); + ViewPort dstView = stage.rowCache[static_cast(cacheIndex)].view(); + const uint8_t *srcData = static_cast(srcView.data); + uint8_t *dstData = static_cast(dstView.data); + int_fast16_t srcWidth = static_cast(srcView.width); + + // キャッシュをゼロクリア + std::memset(dstData, 0, static_cast(cacheWidth_) * 4); + + // コピー範囲の計算(pull pathのfetchRowToStageCacheと同じロジック) + // xOffset > 0: 入力がキャッシュより右にある → cache[xOffset]に書き込み + // xOffset < 0: 入力がキャッシュより左にある → source[-xOffset]から読み込み + int_fast16_t dstStart = std::max(0, xOffset); + int_fast16_t srcStart = std::max(0, -xOffset); + int_fast16_t copyWidth = std::min(srcWidth - srcStart, cacheWidth_ - dstStart); + + if (copyWidth > 0) { + std::memcpy(dstData + dstStart * 4, srcData + srcStart * 4, static_cast(copyWidth) * 4); + } } -} // namespace FLEXIMG_NAMESPACE +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_VERTICAL_BLUR_NODE_H +#endif // FLEXIMG_VERTICAL_BLUR_NODE_H diff --git a/src/fleximg/operations/canvas_utils.h b/src/fleximg/operations/canvas_utils.h index 651d4ce..49c0ff4 100644 --- a/src/fleximg/operations/canvas_utils.h +++ b/src/fleximg/operations/canvas_utils.h @@ -24,95 +24,85 @@ namespace canvas_utils { // - 全面を画像で埋める場合: DefaultInitPolicy(初期化スキップ可) // - 部分的な描画の場合: InitPolicy::Zero(透明で初期化) // alloc: メモリアロケータ(nullptrの場合はDefaultAllocator使用) -inline ImageBuffer createCanvas(int_fast16_t width, int_fast16_t height, - InitPolicy init = DefaultInitPolicy, - core::memory::IAllocator *alloc = nullptr) { - return ImageBuffer(width, height, PixelFormatIDs::RGBA8_Straight, init, - alloc); +inline ImageBuffer createCanvas(int_fast16_t width, int_fast16_t height, InitPolicy init = DefaultInitPolicy, + core::memory::IAllocator *alloc = nullptr) +{ + return ImageBuffer(width, height, PixelFormatIDs::RGBA8_Straight, init, alloc); } // 最初の画像をキャンバスに配置 // 透明キャンバスへの最初の描画(ブレンド不要、変換コピーのみ) // PixelFormatDescriptorの変換関数を使用 -inline void placeFirst(ViewPort &canvas, int_fixed canvasOriginX, - int_fixed canvasOriginY, const ViewPort &src, - int_fixed srcOriginX, int_fixed srcOriginY) { - if (!canvas.isValid() || !src.isValid()) - return; - - // 新座標系: originはバッファ左上のワールド座標 - // srcをcanvasに配置する際のオフセット = src左端 - canvas左端 - auto offsetX = - static_cast(from_fixed(srcOriginX - canvasOriginX)); - auto offsetY = - static_cast(from_fixed(srcOriginY - canvasOriginY)); - - // クリッピング範囲を計算 - // offsetX > 0: srcがcanvasより右にある → canvas[offsetX]に書き込み - // offsetX < 0: srcがcanvasより左にある → src[-offsetX]から読み込み - auto srcStartX = std::max(0, -offsetX); - auto srcStartY = std::max(0, -offsetY); - auto dstStartX = std::max(0, offsetX); - auto dstStartY = std::max(0, offsetY); - auto copyWidth = - std::min(src.width - srcStartX, canvas.width - dstStartX); - auto copyHeight = - std::min(src.height - srcStartY, canvas.height - dstStartY); - - if (copyWidth <= 0 || copyHeight <= 0) - return; - - // 同一フォーマット → memcpy - if (src.formatID == canvas.formatID) { - size_t bytesPerPixel = static_cast(src.formatID->bytesPerPixel); - for (int_fast16_t y = 0; y < copyHeight; y++) { - const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); - void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); - std::memcpy(dstRow, srcRow, - static_cast(copyWidth) * bytesPerPixel); +inline void placeFirst(ViewPort &canvas, int_fixed canvasOriginX, int_fixed canvasOriginY, const ViewPort &src, + int_fixed srcOriginX, int_fixed srcOriginY) +{ + if (!canvas.isValid() || !src.isValid()) return; + + // 新座標系: originはバッファ左上のワールド座標 + // srcをcanvasに配置する際のオフセット = src左端 - canvas左端 + auto offsetX = static_cast(from_fixed(srcOriginX - canvasOriginX)); + auto offsetY = static_cast(from_fixed(srcOriginY - canvasOriginY)); + + // クリッピング範囲を計算 + // offsetX > 0: srcがcanvasより右にある → canvas[offsetX]に書き込み + // offsetX < 0: srcがcanvasより左にある → src[-offsetX]から読み込み + auto srcStartX = std::max(0, -offsetX); + auto srcStartY = std::max(0, -offsetY); + auto dstStartX = std::max(0, offsetX); + auto dstStartY = std::max(0, offsetY); + auto copyWidth = std::min(src.width - srcStartX, canvas.width - dstStartX); + auto copyHeight = std::min(src.height - srcStartY, canvas.height - dstStartY); + + if (copyWidth <= 0 || copyHeight <= 0) return; + + // 同一フォーマット → memcpy + if (src.formatID == canvas.formatID) { + size_t bytesPerPixel = static_cast(src.formatID->bytesPerPixel); + for (int_fast16_t y = 0; y < copyHeight; y++) { + const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); + void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); + std::memcpy(dstRow, srcRow, static_cast(copyWidth) * bytesPerPixel); + } + return; } - return; - } - - // キャンバスがRGBA8_Straight → toStraight関数を使用 - if (canvas.formatID == PixelFormatIDs::RGBA8_Straight && - src.formatID->toStraight) { - for (int_fast16_t y = 0; y < copyHeight; y++) { - const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); - void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); - src.formatID->toStraight(dstRow, srcRow, static_cast(copyWidth), - nullptr); + + // キャンバスがRGBA8_Straight → toStraight関数を使用 + if (canvas.formatID == PixelFormatIDs::RGBA8_Straight && src.formatID->toStraight) { + for (int_fast16_t y = 0; y < copyHeight; y++) { + const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); + void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); + src.formatID->toStraight(dstRow, srcRow, static_cast(copyWidth), nullptr); + } + return; + } + + // フォールバック: convertFormat経由(2段階変換の可能性あり) + for (int y = 0; y < copyHeight; y++) { + const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); + void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); + convertFormat(srcRow, src.formatID, dstRow, canvas.formatID, copyWidth, nullptr); } - return; - } - - // フォールバック: convertFormat経由(2段階変換の可能性あり) - for (int y = 0; y < copyHeight; y++) { - const void *srcRow = src.pixelAt(srcStartX, srcStartY + y); - void *dstRow = canvas.pixelAt(dstStartX, dstStartY + y); - convertFormat(srcRow, src.formatID, dstRow, canvas.formatID, copyWidth, - nullptr); - } } // フォーマット変換(必要なら) // blend関数が対応していないフォーマットをRGBA8_Straightに変換 -inline void ensureBlendableFormat(RenderResponse &input) { - if (!input.isValid()) { - return; - } - - PixelFormatID inputFmt = input.view().formatID; - if (inputFmt == PixelFormatIDs::RGBA8_Straight) { - // 対応フォーマットならそのまま - return; - } - - // RGBA8_Straight に変換 - input.convertFormat(PixelFormatIDs::RGBA8_Straight); +inline void ensureBlendableFormat(RenderResponse &input) +{ + if (!input.isValid()) { + return; + } + + PixelFormatID inputFmt = input.view().formatID; + if (inputFmt == PixelFormatIDs::RGBA8_Straight) { + // 対応フォーマットならそのまま + return; + } + + // RGBA8_Straight に変換 + input.convertFormat(PixelFormatIDs::RGBA8_Straight); } -} // namespace canvas_utils -} // namespace FLEXIMG_NAMESPACE +} // namespace canvas_utils +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_CANVAS_UTILS_H +#endif // FLEXIMG_CANVAS_UTILS_H diff --git a/src/fleximg/operations/filters.h b/src/fleximg/operations/filters.h index 4fe3a24..1a8871a 100644 --- a/src/fleximg/operations/filters.h +++ b/src/fleximg/operations/filters.h @@ -17,13 +17,12 @@ namespace filters { /// ラインフィルタ共通パラメータ struct LineFilterParams { - float value1 = 0.0f; ///< brightness amount, alpha scale 等 - float value2 = 0.0f; ///< 将来の拡張用 + float value1 = 0.0f; ///< brightness amount, alpha scale 等 + float value2 = 0.0f; ///< 将来の拡張用 }; /// ラインフィルタ関数型(RGBA8_Straight形式、インプレース処理) -using LineFilterFunc = void (*)(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms); +using LineFilterFunc = void (*)(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms); // ======================================================================== // ラインフィルタ関数(スキャンライン処理用) @@ -35,21 +34,18 @@ using LineFilterFunc = void (*)(uint8_t *pixels, int_fast16_t count, /// 明るさ調整(ラインフィルタ版) /// params.value1: 明るさ調整量(-1.0〜1.0、0.5で+127相当) -void brightness_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms); +void brightness_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms); /// グレースケール変換(ラインフィルタ版) /// パラメータ未使用(将来の拡張用に引数は維持) -void grayscale_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms); +void grayscale_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms); /// アルファ調整(ラインフィルタ版) /// params.value1: アルファスケール(0.0〜1.0) -void alpha_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms); +void alpha_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms); -} // namespace filters -} // namespace FLEXIMG_NAMESPACE +} // namespace filters +} // namespace FLEXIMG_NAMESPACE // ============================================================================= // 実装部 @@ -66,57 +62,55 @@ namespace filters { // ラインフィルタ関数(スキャンライン処理用) // ======================================================================== -void brightness_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms) { - auto adjustment = static_cast(params.value1 * 255.0f); - - for (int_fast16_t x = 0; x < count; x++) { - int_fast16_t pixelOffset = x * 4; - // RGB各チャンネルに明るさ調整を適用 - for (int_fast16_t c = 0; c < 3; c++) { - auto value = - static_cast(pixels[pixelOffset + c] + adjustment); - pixels[pixelOffset + c] = static_cast( - std::max(0, std::min(255, value))); +void brightness_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms) +{ + auto adjustment = static_cast(params.value1 * 255.0f); + + for (int_fast16_t x = 0; x < count; x++) { + int_fast16_t pixelOffset = x * 4; + // RGB各チャンネルに明るさ調整を適用 + for (int_fast16_t c = 0; c < 3; c++) { + auto value = static_cast(pixels[pixelOffset + c] + adjustment); + pixels[pixelOffset + c] = + static_cast(std::max(0, std::min(255, value))); + } + // Alphaはそのまま維持 } - // Alphaはそのまま維持 - } } -void grayscale_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms) { - (void)params; // 将来の拡張用に引数は維持 - - for (int_fast16_t x = 0; x < count; x++) { - int_fast16_t pixelOffset = x * 4; - // グレースケール変換(平均法) - uint8_t gray = - static_cast((static_cast(pixels[pixelOffset]) + - static_cast(pixels[pixelOffset + 1]) + - static_cast(pixels[pixelOffset + 2])) / - 3); - pixels[pixelOffset] = gray; // R - pixels[pixelOffset + 1] = gray; // G - pixels[pixelOffset + 2] = gray; // B - // Alphaはそのまま維持 - } +void grayscale_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms) +{ + (void)params; // 将来の拡張用に引数は維持 + + for (int_fast16_t x = 0; x < count; x++) { + int_fast16_t pixelOffset = x * 4; + // グレースケール変換(平均法) + uint8_t gray = static_cast((static_cast(pixels[pixelOffset]) + + static_cast(pixels[pixelOffset + 1]) + + static_cast(pixels[pixelOffset + 2])) / + 3); + pixels[pixelOffset] = gray; // R + pixels[pixelOffset + 1] = gray; // G + pixels[pixelOffset + 2] = gray; // B + // Alphaはそのまま維持 + } } -void alpha_line(uint8_t *pixels, int_fast16_t count, - const LineFilterParams ¶ms) { - uint32_t alphaScale = static_cast(params.value1 * 256.0f); +void alpha_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams ¶ms) +{ + uint32_t alphaScale = static_cast(params.value1 * 256.0f); - for (int_fast16_t x = 0; x < count; x++) { - int_fast16_t pixelOffset = x * 4; - // RGBはそのまま、Alphaのみスケール - uint32_t a = pixels[pixelOffset + 3]; - pixels[pixelOffset + 3] = static_cast((a * alphaScale) >> 8); - } + for (int_fast16_t x = 0; x < count; x++) { + int_fast16_t pixelOffset = x * 4; + // RGBはそのまま、Alphaのみスケール + uint32_t a = pixels[pixelOffset + 3]; + pixels[pixelOffset + 3] = static_cast((a * alphaScale) >> 8); + } } -} // namespace filters -} // namespace FLEXIMG_NAMESPACE +} // namespace filters +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_IMPLEMENTATION +#endif // FLEXIMG_IMPLEMENTATION -#endif // FLEXIMG_OPERATIONS_FILTERS_H +#endif // FLEXIMG_OPERATIONS_FILTERS_H diff --git a/src/fleximg/operations/transform.h b/src/fleximg/operations/transform.h index 5d5d870..f416346 100644 --- a/src/fleximg/operations/transform.h +++ b/src/fleximg/operations/transform.h @@ -38,75 +38,74 @@ namespace transform { // - {dxStart, dxEnd}: 有効範囲(dxStart > dxEnd なら有効ピクセルなし) // -inline std::pair calcValidRange(int_fixed coeff, int_fixed base, - int srcSize, int canvasSize) { - constexpr int BITS = INT_FIXED_SHIFT; +inline std::pair calcValidRange(int_fixed coeff, int_fixed base, int srcSize, int canvasSize) +{ + constexpr int BITS = INT_FIXED_SHIFT; - // DDAでは (coeff >> 1) のオフセットが加算される - int_fixed baseWithHalf = base + (coeff >> 1); + // DDAでは (coeff >> 1) のオフセットが加算される + int_fixed baseWithHalf = base + (coeff >> 1); - if (coeff == 0) { - // 係数ゼロ:全 dx で同じ srcIdx - int srcIdx = baseWithHalf >> BITS; - return (srcIdx >= 0 && srcIdx < srcSize) ? std::make_pair(0, canvasSize - 1) - : std::make_pair(1, 0); - } + if (coeff == 0) { + // 係数ゼロ:全 dx で同じ srcIdx + int srcIdx = baseWithHalf >> BITS; + return (srcIdx >= 0 && srcIdx < srcSize) ? std::make_pair(0, canvasSize - 1) : std::make_pair(1, 0); + } - // srcIdx の有効範囲: [0, srcSize) - // srcIdx = (coeff * dx + baseWithHalf) >> BITS - // - // 条件: 0 <= srcIdx < srcSize - // → 0 <= (coeff * dx + baseWithHalf) >> BITS < srcSize - // - // 整数右シフトは切り捨て(負方向)なので: - // → 0 <= coeff * dx + baseWithHalf < srcSize << BITS - // - // coeff > 0 の場合: - // dx >= -baseWithHalf / coeff → dx >= ceil(-baseWithHalf / coeff) - // dx < (srcSize << BITS) - baseWithHalf) / coeff - // - // coeff < 0 の場合: 不等式の向きが逆転 + // srcIdx の有効範囲: [0, srcSize) + // srcIdx = (coeff * dx + baseWithHalf) >> BITS + // + // 条件: 0 <= srcIdx < srcSize + // → 0 <= (coeff * dx + baseWithHalf) >> BITS < srcSize + // + // 整数右シフトは切り捨て(負方向)なので: + // → 0 <= coeff * dx + baseWithHalf < srcSize << BITS + // + // coeff > 0 の場合: + // dx >= -baseWithHalf / coeff → dx >= ceil(-baseWithHalf / coeff) + // dx < (srcSize << BITS) - baseWithHalf) / coeff + // + // coeff < 0 の場合: 不等式の向きが逆転 - int64_t minBound = -static_cast(baseWithHalf); - int64_t maxBound = (static_cast(srcSize) << BITS) - baseWithHalf; + int64_t minBound = -static_cast(baseWithHalf); + int64_t maxBound = (static_cast(srcSize) << BITS) - baseWithHalf; - int dxStart, dxEnd; - if (coeff > 0) { - // dx >= ceil(minBound / coeff) かつ dx < maxBound / coeff - // → dx >= ceil(minBound / coeff) かつ dx <= floor((maxBound - 1) / coeff) - if (minBound >= 0) { - dxStart = static_cast((minBound + coeff - 1) / coeff); - } else { - // 負の除算: ceil(a/b) = -(-a / b) for a < 0, b > 0 - dxStart = static_cast(-(-minBound / coeff)); - } - if (maxBound > 0) { - dxEnd = static_cast((maxBound - 1) / coeff); - } else { - // maxBound <= 0 → 有効範囲なし - return {1, 0}; - } - } else { - // coeff < 0: 不等式の向きが逆転 - // dx <= floor(minBound / coeff) かつ dx > (maxBound - 1) / coeff - int_fixed negCoeff = -coeff; - if (minBound <= 0) { - dxEnd = static_cast((-minBound) / negCoeff); - } else { - dxEnd = static_cast(-(minBound + negCoeff - 1) / negCoeff); - } - if (maxBound <= 0) { - dxStart = static_cast((-maxBound + 1 + negCoeff - 1) / negCoeff); + int dxStart, dxEnd; + if (coeff > 0) { + // dx >= ceil(minBound / coeff) かつ dx < maxBound / coeff + // → dx >= ceil(minBound / coeff) かつ dx <= floor((maxBound - 1) / coeff) + if (minBound >= 0) { + dxStart = static_cast((minBound + coeff - 1) / coeff); + } else { + // 負の除算: ceil(a/b) = -(-a / b) for a < 0, b > 0 + dxStart = static_cast(-(-minBound / coeff)); + } + if (maxBound > 0) { + dxEnd = static_cast((maxBound - 1) / coeff); + } else { + // maxBound <= 0 → 有効範囲なし + return {1, 0}; + } } else { - // maxBound > 0 かつ coeff < 0 → 全dx有効の可能性 - dxStart = static_cast(-((maxBound - 1) / negCoeff)); + // coeff < 0: 不等式の向きが逆転 + // dx <= floor(minBound / coeff) かつ dx > (maxBound - 1) / coeff + int_fixed negCoeff = -coeff; + if (minBound <= 0) { + dxEnd = static_cast((-minBound) / negCoeff); + } else { + dxEnd = static_cast(-(minBound + negCoeff - 1) / negCoeff); + } + if (maxBound <= 0) { + dxStart = static_cast((-maxBound + 1 + negCoeff - 1) / negCoeff); + } else { + // maxBound > 0 かつ coeff < 0 → 全dx有効の可能性 + dxStart = static_cast(-((maxBound - 1) / negCoeff)); + } } - } - return {dxStart, dxEnd}; + return {dxStart, dxEnd}; } -} // namespace transform -} // namespace FLEXIMG_NAMESPACE +} // namespace transform +} // namespace FLEXIMG_NAMESPACE -#endif // FLEXIMG_OPERATIONS_TRANSFORM_H +#endif // FLEXIMG_OPERATIONS_TRANSFORM_H