diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b5d39c..0a285ec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,18 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Check clang-format + - name: Check clang-format (src) uses: jidicula/clang-format-action@v4.14.0 with: clang-format-version: '21' check-path: 'src' + - name: Check clang-format (impl) + uses: jidicula/clang-format-action@v4.14.0 + with: + clang-format-version: '21' + check-path: 'impl' + test: runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index bed9a7e..9f3b8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed +- **宣言/実装の物理分離** + - `src/fleximg/` を宣言のみ(公開ヘッダ)、`impl/fleximg/` に実装(`.inl`)を配置 + - `#ifdef FLEXIMG_IMPLEMENTATION` パターンを廃止し、`fleximg.cpp` が両方をインクルードする構成に変更 + - Arduino ライブラリとして安全にインクルード可能に(実装の重複コンパイルを防止) + - `dda.h`, `format_converter.h` を削除(実装は `.inl` に移行) + - examples の `#define FLEXIMG_IMPLEMENTATION` を削除、`platformio.ini` で `fleximg.cpp` をビルド対象に追加 + +- **common.h: デバッグディレイのプラットフォーム対応改善** + - `__has_include` による FreeRTOS 自動検出(ESP-IDF 直接利用にも対応) + - FreeRTOS なし Arduino 環境では `delay()` にフォールバック + - **WebUI: C++同期型定義を `cpp-sync-types.js` に分離** - `NODE_TYPES`, `PIXEL_FORMATS`, `DEFAULT_PIXEL_FORMAT` 等のC++側と手動同期が必要な定義を `app.js` から `demo/web/cpp-sync-types.js` に分離 - `buildFormatOptions()`, `NodeTypeHelper` も同ファイルに移動 diff --git a/CLAUDE.md b/CLAUDE.md index a2aea37..0c3523f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,11 +10,16 @@ ### 主要ディレクトリ ``` -src/fleximg/ # コアライブラリ -├── nodes/ # ノード実装(Source, Composite, Filter等) +src/fleximg/ # 公開ヘッダ(宣言のみ) +├── nodes/ # ノード宣言 ├── image/ # ImageBuffer, PixelFormat, ViewPort -├── operations/ # blend, filters, transform +├── operations/ # filters, transform └── core/memory/ # アロケータ、プール管理 +impl/fleximg/ # 実装ファイル(.inl、非公開) +├── nodes/ # ノード実装 +├── image/ # PixelFormat, ViewPort 実装 +├── operations/ # filters 実装 +└── core/memory/ # メモリ管理実装 examples/ # サンプルコード ├── bench/ # ベンチマーク(native/M5Stack両対応) ├── m5stack_basic/ # M5Stack基本サンプル diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 26f8b15..bc95a92 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -393,66 +393,47 @@ namespace canvas_utils { ## ビルド方式 -### stb-style(Implementation Macro パターン) +### 宣言/実装分離パターン -fleximg は [stb ライブラリ](https://github.com/nothings/stb) と同様の実装マクロパターンを採用しています。 -これにより、コンパイル単位を最小化し、Arduino IDE などでのビルド時間を短縮します。 +fleximg は宣言(`.h`)と実装(`.inl`)を物理的に分離しています。 +`src/fleximg/` に公開ヘッダ(宣言のみ)、`impl/fleximg/` に実装ファイルを配置し、 +単一コンパイル単位 `fleximg.cpp` から両方をインクルードします。 -**使用方法:** +この構造により: +- Arduino IDE 等で `src/` 配下のヘッダを安全にインクルード可能 +- 実装の重複コンパイルを防止 +- コンパイル単位を最小化し、ビルド時間を短縮 + +**使用方法(ユーザー側):** ```cpp -// 1つのソースファイルでのみ FLEXIMG_IMPLEMENTATION を定義 +// ヘッダのインクルードのみ(実装は fleximg.cpp に含まれる) #define FLEXIMG_NAMESPACE fleximg -#define FLEXIMG_IMPLEMENTATION -#include "fleximg/core/memory/platform.h" -#include "fleximg/core/memory/pool_allocator.h" #include "fleximg/image/pixel_format.h" -#include "fleximg/image/viewport.h" -#include "fleximg/operations/filters.h" -// ... 使用するヘッダをインクルード - -// 他のソースファイルでは FLEXIMG_IMPLEMENTATION を定義しない -#define FLEXIMG_NAMESPACE fleximg -#include "fleximg/image/pixel_format.h" // 宣言のみ使用 +#include "fleximg/nodes/source_node.h" +// ... ``` -**WASM/テストビルド:** -`src/fleximg/fleximg.cpp` が唯一のコンパイル単位として全実装を含みます。 - -**実装分離の方針:** - -各ヘッダファイルは以下の構造を持ちます: +**コンパイル単位:** +`src/fleximg/fleximg.cpp` が唯一のコンパイル単位として、宣言ヘッダと `.inl` 実装ファイルの両方をインクルードします。 ```cpp -#ifndef FLEXIMG_XXX_H -#define FLEXIMG_XXX_H - -// クラス宣言(ヘッダ部) -// - コンストラクタ、デストラクタ -// - 短いアクセサ(1-2行) -// - 短いpublicメソッド - -#ifdef FLEXIMG_IMPLEMENTATION -// 実装部 -// - 仮想オーバーライドメソッド(vtable linkage問題の回避) -// - privateヘルパーメソッド -// - 複雑なロジック -#endif - -#endif +// 宣言ヘッダ(src/fleximg/ 内) +#include "core/node.h" +#include "image/pixel_format.h" +// ... + +// 実装ファイル(impl/fleximg/ 内) +#include "../../impl/fleximg/core/node.inl" +#include "../../impl/fleximg/image/pixel_format.inl" +// ... ``` -**実装を含むファイル一覧:** -- `core/node.h`, `core/memory/platform.h`, `core/memory/pool_allocator.h` -- `image/pixel_format.h`, `image/viewport.h` -- `operations/filters.h` -- 全ノードファイル(`nodes/*.h`) - ## ファイル構成 ``` -src/fleximg/ -├── fleximg.cpp # メインコンパイル単位(stb-style) +src/fleximg/ # 公開ヘッダ(宣言のみ) +├── fleximg.cpp # メインコンパイル単位 │ ├── core/ # コア機能(fleximg::core 名前空間) │ ├── common.h # NAMESPACE定義、バージョン @@ -471,19 +452,19 @@ src/fleximg/ │ ├── image/ # 画像処理 │ ├── pixel_format.h # ピクセルフォーマット共通定義・ユーティリティ -│ ├── pixel_format/ # 各フォーマットの個別実装 +│ ├── pixel_format/ # 各フォーマットの個別宣言 │ │ ├── rgba8_straight.h # RGBA8_Straight │ │ ├── alpha8.h # Alpha8 │ │ ├── rgb565.h # RGB565_LE/BE + ルックアップテーブル + swap16 │ │ ├── rgb332.h # RGB332 + ルックアップテーブル │ │ ├── rgb888.h # RGB888/BGR888 + swap24 -│ │ ├── grayscale8.h # Grayscale8(BT.601輝度) -│ │ └── index8.h # Index8(パレットインデックス) +│ │ ├── grayscale.h # Grayscale(BT.601輝度) +│ │ └── index.h # Index(パレットインデックス) │ ├── viewport.h # ViewPort │ ├── image_buffer.h # ImageBuffer │ └── render_types.h # RenderRequest, RenderResponse │ -├── nodes/ +├── nodes/ # ノード宣言 │ ├── source_node.h # SourceNode │ ├── ninepatch_source_node.h # NinePatchSourceNode(9パッチ画像) │ ├── sink_node.h # SinkNode @@ -503,6 +484,40 @@ src/fleximg/ ├── transform.h # アフィン変換(DDA処理) ├── filters.h # フィルタ処理 └── canvas_utils.h # キャンバス操作(合成ユーティリティ) + +impl/fleximg/ # 実装ファイル(.inl、非公開) +├── core/ +│ ├── node.inl +│ └── memory/ +│ ├── platform.inl +│ └── pool_allocator.inl +├── image/ +│ ├── pixel_format.inl # 集約ファイル(サブフォーマット .inl をインクルード) +│ ├── pixel_format/ +│ │ ├── alpha8.inl +│ │ ├── grayscale.inl +│ │ ├── index.inl +│ │ ├── rgb332.inl +│ │ ├── rgb565.inl +│ │ ├── rgb888.inl +│ │ ├── rgba8_straight.inl +│ │ ├── dda.inl +│ │ └── format_converter.inl +│ └── viewport.inl +├── operations/ +│ └── filters.inl +└── nodes/ + ├── affine_node.inl + ├── composite_node.inl + ├── distributor_node.inl + ├── filter_node_base.inl + ├── horizontal_blur_node.inl + ├── matte_node.inl + ├── ninepatch_source_node.inl + ├── renderer_node.inl + ├── sink_node.inl + ├── source_node.inl + └── vertical_blur_node.inl ``` ## 使用例 @@ -510,16 +525,10 @@ src/fleximg/ ### 基本的なパイプライン ```cpp -// stb-style: 実装を有効化 #define FLEXIMG_NAMESPACE fleximg -#define FLEXIMG_IMPLEMENTATION -#include "fleximg/core/common.h" -#include "fleximg/core/memory/platform.h" -#include "fleximg/core/memory/pool_allocator.h" #include "fleximg/image/pixel_format.h" #include "fleximg/image/viewport.h" #include "fleximg/image/image_buffer.h" -#include "fleximg/operations/filters.h" #include "fleximg/nodes/source_node.h" #include "fleximg/nodes/sink_node.h" #include "fleximg/nodes/affine_node.h" diff --git a/examples/bench/src/main.cpp b/examples/bench/src/main.cpp index 203cba2..5833f4a 100644 --- a/examples/bench/src/main.cpp +++ b/examples/bench/src/main.cpp @@ -46,10 +46,9 @@ #include #endif -// fleximg (stb-style: define FLEXIMG_IMPLEMENTATION before including headers) +// fleximg #define FLEXIMG_NAMESPACE fleximg #define FLEXIMG_DEBUG_MOVE_COUNT // ムーブ回数カウンタ有効化 -#define FLEXIMG_IMPLEMENTATION #include "fleximg/core/common.h" #include "fleximg/core/memory/allocator.h" #include "fleximg/core/memory/pool_allocator.h" diff --git a/examples/m5stack_basic/src/main.cpp b/examples/m5stack_basic/src/main.cpp index e5ca0c4..0ef2a86 100644 --- a/examples/m5stack_basic/src/main.cpp +++ b/examples/m5stack_basic/src/main.cpp @@ -4,22 +4,19 @@ #include -// fleximg (stb-style: define FLEXIMG_IMPLEMENTATION before including headers) +// fleximg #define FLEXIMG_NAMESPACE fleximg -#define FLEXIMG_IMPLEMENTATION #include "fleximg/core/common.h" #include "fleximg/core/memory/platform.h" +#include "fleximg/core/memory/pool_allocator.h" #include "fleximg/core/types.h" #include "fleximg/image/image_buffer.h" +#include "fleximg/image/pixel_format.h" #include "fleximg/image/viewport.h" #include "fleximg/nodes/affine_node.h" #include "fleximg/nodes/composite_node.h" #include "fleximg/nodes/renderer_node.h" #include "fleximg/nodes/source_node.h" - -// stb 方式: FLEXIMG_IMPLEMENTATION 定義済みなのでヘッダから実装が有効化される -#include "fleximg/core/memory/pool_allocator.h" -#include "fleximg/image/pixel_format.h" #include "fleximg/operations/filters.h" // カスタムSinkNode diff --git a/examples/m5stack_hos/src/main.cpp b/examples/m5stack_hos/src/main.cpp index 4e78c5a..ff89a4b 100644 --- a/examples/m5stack_hos/src/main.cpp +++ b/examples/m5stack_hos/src/main.cpp @@ -5,22 +5,20 @@ #include -// fleximg (stb-style: define FLEXIMG_IMPLEMENTATION before including headers) +// fleximg #define FLEXIMG_NAMESPACE fleximg -#define FLEXIMG_IMPLEMENTATION #include "fleximg/core/common.h" #include "fleximg/core/memory/platform.h" +#include "fleximg/core/memory/pool_allocator.h" #include "fleximg/core/types.h" #include "fleximg/image/image_buffer.h" +#include "fleximg/image/pixel_format.h" #include "fleximg/image/viewport.h" #include "fleximg/nodes/affine_node.h" #include "fleximg/nodes/composite_node.h" #include "fleximg/nodes/renderer_node.h" #include "fleximg/nodes/source_node.h" -#include "fleximg/core/memory/pool_allocator.h" -#include "fleximg/image/pixel_format.h" - #include "lcd_sink_node.h" #include diff --git a/examples/m5stack_matte/src/main.cpp b/examples/m5stack_matte/src/main.cpp index af140d1..1e2c0c7 100644 --- a/examples/m5stack_matte/src/main.cpp +++ b/examples/m5stack_matte/src/main.cpp @@ -3,22 +3,19 @@ #include -// fleximg (stb-style: define FLEXIMG_IMPLEMENTATION before including headers) +// fleximg #define FLEXIMG_NAMESPACE fleximg -#define FLEXIMG_IMPLEMENTATION #include "fleximg/core/common.h" #include "fleximg/core/memory/platform.h" +#include "fleximg/core/memory/pool_allocator.h" #include "fleximg/core/types.h" #include "fleximg/image/image_buffer.h" +#include "fleximg/image/pixel_format.h" #include "fleximg/image/viewport.h" #include "fleximg/nodes/affine_node.h" #include "fleximg/nodes/matte_node.h" #include "fleximg/nodes/renderer_node.h" #include "fleximg/nodes/source_node.h" - -// stb 方式: FLEXIMG_IMPLEMENTATION 定義済みなのでヘッダから実装が有効化される -#include "fleximg/core/memory/pool_allocator.h" -#include "fleximg/image/pixel_format.h" #include "fleximg/operations/filters.h" // カスタムSinkNode diff --git a/impl/fleximg/core/memory/platform.inl b/impl/fleximg/core/memory/platform.inl new file mode 100644 index 0000000..8be71bb --- /dev/null +++ b/impl/fleximg/core/memory/platform.inl @@ -0,0 +1,42 @@ +/** + * @file platform.inl + * @brief プラットフォーム固有のメモリ確保 実装 + * @see src/fleximg/core/memory/platform.h + */ + +#include "../../../../src/fleximg/core/memory/allocator.h" + +namespace FLEXIMG_NAMESPACE { +namespace core { +namespace memory { + +// グローバルプラットフォームメモリインスタンス +static IPlatformMemory *s_platformMemory = nullptr; + +IPlatformMemory &getPlatformMemory() +{ + if (!s_platformMemory) { + s_platformMemory = &DefaultPlatformMemory::instance(); + } + return *s_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::deallocate(void *ptr) +{ + DefaultAllocator::instance().deallocate(ptr); +} + +} // namespace memory +} // namespace core +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/core/memory/pool_allocator.inl b/impl/fleximg/core/memory/pool_allocator.inl new file mode 100644 index 0000000..12b3cd7 --- /dev/null +++ b/impl/fleximg/core/memory/pool_allocator.inl @@ -0,0 +1,161 @@ +/** + * @file pool_allocator.inl + * @brief ビットマップベースのプールアロケータ 実装 + * @see src/fleximg/core/memory/pool_allocator.h + */ + +namespace FLEXIMG_NAMESPACE { +namespace core { +namespace memory { + +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; +} + +void *PoolAllocator::allocate(size_t size) +{ + if (!initialized_ || size == 0) { + return nullptr; + } + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + stats_.totalAllocations++; +#endif + + // 必要なブロック数を計算 + size_t blocksNeeded = (size + blockSize_ - 1) / blockSize_; + + if (blocksNeeded > blockCount_) { +#ifdef FLEXIMG_DEBUG_PERF_METRICS + stats_.misses++; +#endif + return nullptr; + } + + // 必要なビットパターンを作成 + uint32_t needBitmap = (1U << blocksNeeded) - 1; + + // 探索方向を決定(交互に切り替えてフラグメンテーション軽減) + size_t start = searchFromHead_ ? 0 : blockCount_ - blocksNeeded; + size_t end = blockCount_ - blocksNeeded + 1; + bool forward = searchFromHead_; + + searchFromHead_ = !searchFromHead_; // 次回は逆方向 + + // ビットマップで連続空きブロックを探索 + 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); // 確保ブロック数を記録 +#ifdef FLEXIMG_DEBUG_PERF_METRICS + 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_); + } + } + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + stats_.misses++; +#endif + return nullptr; +} + +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); + + if (p < poolStart || p >= poolEnd) { + return false; // プール外 + } + + // ブロックインデックス計算 + size_t blockIndex = static_cast(p - poolStart) / blockSize_; + + if (blockIndex >= blockCount_) { + return false; // 範囲外 + } + + // ビットが立っているか確認(確保済みか) + if ((allocatedBitmap_ & (1U << blockIndex)) == 0) { + return false; // 二重解放 + } + + // 確保ブロック数を取得 + uint8_t blocksToFree = blockCounts_[blockIndex]; + if (blocksToFree == 0) { + blocksToFree = 1; // フォールバック(通常は起きない) + } + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + stats_.totalDeallocations++; +#endif + + // 確保時のブロック数分のビットをクリア + uint32_t freeBitmap = ((1U << blocksToFree) - 1) << blockIndex; + allocatedBitmap_ &= ~freeBitmap; + blockCounts_[blockIndex] = 0; // 記録をクリア +#ifdef FLEXIMG_DEBUG_PERF_METRICS + stats_.allocatedBitmap = allocatedBitmap_; +#endif + + return true; +} + +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 diff --git a/impl/fleximg/core/node.inl b/impl/fleximg/core/node.inl new file mode 100644 index 0000000..4c238bb --- /dev/null +++ b/impl/fleximg/core/node.inl @@ -0,0 +1,124 @@ +/** + * @file node.inl + * @brief Node クラスの実装 + * @see src/fleximg/core/node.h + */ + +namespace FLEXIMG_NAMESPACE { +namespace core { + +// ============================================================================ +// Node - ヘルパーメソッド実装 +// ============================================================================ + +// 循環参照チェック(pullPrepare/pushPrepare共通) +// 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; // 成功(処理継続) +} + +// フォーマット変換ヘルパー(メトリクス記録付き) +// 参照モードから所有モードに変わった場合、ノード別統計に記録 +// 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()) { +#ifdef FLEXIMG_DEBUG_PERF_METRICS + PerfMetrics::instance().nodes[nodeTypeForMetrics()].recordAlloc(result.totalBytes(), result.width(), + result.height()); +#endif + } + 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::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を構築 +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 diff --git a/impl/fleximg/image/pixel_format.inl b/impl/fleximg/image/pixel_format.inl new file mode 100644 index 0000000..3909909 --- /dev/null +++ b/impl/fleximg/image/pixel_format.inl @@ -0,0 +1,65 @@ +/** + * @file pixel_format.inl + * @brief PixelFormat 実装集約 + * @see src/fleximg/image/pixel_format.h + */ + +// pixel_format.h 自身の実装(lut8toN テンプレート) + +namespace FLEXIMG_NAMESPACE { +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); +} + +// 明示的インスタンス化(非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 *); + +} // namespace detail +} // namespace pixel_format +} // namespace FLEXIMG_NAMESPACE + +// 各フォーマット実装 +#include "pixel_format/alpha8.inl" +#include "pixel_format/grayscale.inl" +#include "pixel_format/index.inl" +#include "pixel_format/rgb332.inl" +#include "pixel_format/rgb565.inl" +#include "pixel_format/rgb888.inl" +#include "pixel_format/rgba8_straight.inl" + +// DDA関数(index.inl の bit_packed_detail 定義後に必要) +#include "pixel_format/dda.inl" + +// FormatConverter 実装 +#include "pixel_format/format_converter.inl" diff --git a/impl/fleximg/image/pixel_format/alpha8.inl b/impl/fleximg/image/pixel_format/alpha8.inl new file mode 100644 index 0000000..6e46539 --- /dev/null +++ b/impl/fleximg/image/pixel_format/alpha8.inl @@ -0,0 +1,71 @@ +/** + * @file alpha8.inl + * @brief Alpha8 ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/alpha8.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// Alpha8: 単一アルファチャンネル <-> RGBA8_Straight 変換 +// ======================================================================== + +// 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 + } +} + +// 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チャンネル抽出 + } +} + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + true, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/src/fleximg/image/pixel_format/dda.h b/impl/fleximg/image/pixel_format/dda.inl similarity index 92% rename from src/fleximg/image/pixel_format/dda.h rename to impl/fleximg/image/pixel_format/dda.inl index 018ae3a..09448d1 100644 --- a/src/fleximg/image/pixel_format/dda.h +++ b/impl/fleximg/image/pixel_format/dda.inl @@ -1,36 +1,8 @@ -#ifndef FLEXIMG_PIXEL_FORMAT_DDA_H -#define FLEXIMG_PIXEL_FORMAT_DDA_H - -// pixel_format.h からインクルードされることを前提 -// (PixelFormatDescriptor、DDAParam、int_fixed等は既に定義済み) - -// ======================================================================== -// DDA (Digital Differential Analyzer) 転写関数 - バイト単位実装 -// ======================================================================== -// -// ピクセルフォーマットに依存しないDDA処理の実装を集約。 -// アフィン変換やバイリニア補間で使用される、高速なピクセル転写関数群。 -// -// **このファイルの内容:** -// - バイト単位のDDA(1/2/3/4 バイト/ピクセル) -// - copyRowDDA_Byte: 行単位ピクセル転写 -// - copyQuadDDA_Byte: 2x2グリッド抽出(バイリニア補間用) -// - ラッパー関数: copyRowDDA_1Byte ~ _4Byte, copyQuadDDA_1Byte ~ _4Byte -// -// **bit-packed DDA関数の場所:** -// - ビット単位のDDA(1/2/4 ビット/ピクセル)は bit_packed_index.h 内に定義 -// - copyRowDDA_Bit -// - copyQuadDDA_Bit -// - これは bit_packed_detail::readPixelDirect への依存を避けるための設計 -// (インクルード順序の複雑さを回避しつつ、命名規則の統一は達成) -// -// **命名規則の統一:** -// - バイト単位: _Byte サフィックス(明示的にバイト数を指定) -// - ビット単位: _Bit サフィックス(明示的にビット数とbit-orderを指定) -// - 対称性: copyRowDDA_Byte<3> vs copyRowDDA_Bit<4, MSBFirst> -// - -#ifdef FLEXIMG_IMPLEMENTATION +/** + * @file dda.inl + * @brief DDA (Digital Differential Analyzer) 転写関数 実装 + * @see src/fleximg/image/pixel_format/dda.h + */ namespace FLEXIMG_NAMESPACE { namespace pixel_format { @@ -40,7 +12,7 @@ namespace detail { // バイト単位のDDA関数(1/2/3/4 バイト/ピクセル) // ======================================================================== -// BytesPerPixel → ネイティブ型マッピング(ロード・ストア分離用) +// BytesPerPixel -> ネイティブ型マッピング(ロード・ストア分離用) // 1, 2, 4 バイトはネイティブ型で直接ロード・ストア可能 // 3 バイトはネイティブ型が存在しないため byte 単位で処理 template @@ -73,7 +45,7 @@ void copyRowDDA_ConstY(uint8_t *__restrict__ dstRow, const uint8_t *__restrict__ // 端数を先に処理し、4ピクセルループを最後に連続実行する if constexpr (BytesPerPixel == 3) { if (count & 1) { - // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) + // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes x 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; @@ -84,7 +56,7 @@ void copyRowDDA_ConstY(uint8_t *__restrict__ dstRow, const uint8_t *__restrict__ } count >>= 1; while (count--) { - // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes × 4pixels) + // BytesPerPixel==3: byte単位でロード・ストア分離(3bytes x 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; @@ -317,7 +289,7 @@ inline void copyRowDDA_4Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t // ============================================================================ // // バイリニア補間に必要な4ピクセル(2x2グリッド)を抽出する。 -// 出力形式: [p00,p10,p01,p11][p00,p10,p01,p11]... × count +// 出力形式: [p00,p10,p01,p11][p00,p10,p01,p11]... x count // 重み情報はparam->weightsに出力される。 // // 最適化: @@ -359,7 +331,7 @@ inline void copyQuadPixels(uint8_t *__restrict__ dst, const uint8_t *p00, const // 4ピクセル抽出(DDAベース、バイリニア補間用) // 境界領域と安全領域の2ブロック構成: -// boundary [0, safeStart) → safe [safeStart, safeEnd) → boundary [safeEnd, +// boundary [0, safeStart) -> safe [safeStart, safeEnd) -> boundary [safeEnd, // count) // fadeFlags は prepareCopyQuadDDA で事前生成済み、この関数では参照・更新しない template @@ -703,7 +675,3 @@ inline void copyQuadDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t c } // namespace detail } // namespace pixel_format } // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - -#endif // FLEXIMG_PIXEL_FORMAT_DDA_H diff --git a/src/fleximg/image/pixel_format/format_converter.h b/impl/fleximg/image/pixel_format/format_converter.inl similarity index 91% rename from src/fleximg/image/pixel_format/format_converter.h rename to impl/fleximg/image/pixel_format/format_converter.inl index 3a994e2..56944b0 100644 --- a/src/fleximg/image/pixel_format/format_converter.h +++ b/impl/fleximg/image/pixel_format/format_converter.inl @@ -1,13 +1,8 @@ -#ifndef FLEXIMG_PIXEL_FORMAT_FORMAT_CONVERTER_H -#define FLEXIMG_PIXEL_FORMAT_FORMAT_CONVERTER_H - -// pixel_format.h の末尾からインクルードされることを前提 -// (FormatConverter, PixelFormatDescriptor, PixelFormatIDs 等は既に定義済み) - -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION +/** + * @file format_converter.inl + * @brief FormatConverter 実装 + * @see src/fleximg/image/pixel_format/format_converter.h + */ namespace FLEXIMG_NAMESPACE { @@ -192,7 +187,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma result.ctx.pixelOffsetInByte = srcAux->pixelOffsetInByte; } - // 同一フォーマット → memcpy + // 同一フォーマット -> memcpy if (srcFormat == dstFormat) { result.ctx.pixelsPerUnit = srcFormat->pixelsPerUnit; result.ctx.bytesPerUnit = srcFormat->bytesPerUnit; @@ -200,7 +195,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma return result; } - // エンディアン兄弟 → swapEndian + // エンディアン兄弟 -> swapEndian if (srcFormat->siblingEndian == dstFormat && srcFormat->swapEndian) { result.ctx.toStraight = srcFormat->swapEndian; result.func = fcv_single; @@ -216,7 +211,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma result.ctx.expandIndex = srcFormat->expandIndex; if (palFmt == dstFormat) { - // 直接展開: Index → パレットフォーマット == 出力フォーマット + // 直接展開: Index -> パレットフォーマット == 出力フォーマット result.func = fcv_expandIndex_direct; return result; } @@ -228,7 +223,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma } if (palFmt == PixelFormatIDs::RGBA8_Straight) { - // expandIndex → fromStraight + // expandIndex -> fromStraight if (dstFormat->fromStraight) { result.ctx.fromStraight = dstFormat->fromStraight; result.func = fcv_expandIndex_fromStraight; @@ -236,7 +231,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma return result; } - // expandIndex → toStraight → fromStraight + // expandIndex -> toStraight -> fromStraight if (palFmt && palFmt->toStraight && dstFormat->fromStraight) { result.ctx.toStraight = palFmt->toStraight; result.ctx.fromStraight = dstFormat->fromStraight; @@ -246,7 +241,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma return result; } - // src == RGBA8 → fromStraight 直接(中間バッファ不要) + // src == RGBA8 -> fromStraight 直接(中間バッファ不要) if (srcFormat == PixelFormatIDs::RGBA8_Straight) { if (dstFormat->fromStraight) { result.ctx.toStraight = dstFormat->fromStraight; @@ -255,7 +250,7 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma return result; } - // dst == RGBA8 → toStraight 直接(中間バッファ不要) + // dst == RGBA8 -> toStraight 直接(中間バッファ不要) if (dstFormat == PixelFormatIDs::RGBA8_Straight) { if (srcFormat->toStraight) { result.ctx.toStraight = srcFormat->toStraight; @@ -283,7 +278,3 @@ FormatConverter resolveConverter(PixelFormatID srcFormat, PixelFormatID dstForma } } // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - -#endif // FLEXIMG_PIXEL_FORMAT_FORMAT_CONVERTER_H diff --git a/impl/fleximg/image/pixel_format/grayscale.inl b/impl/fleximg/image/pixel_format/grayscale.inl new file mode 100644 index 0000000..e43ba58 --- /dev/null +++ b/impl/fleximg/image/pixel_format/grayscale.inl @@ -0,0 +1,404 @@ +/** + * @file grayscale.inl + * @brief Grayscale ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/grayscale.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// Grayscale8: 単一輝度チャンネル <-> RGBA8_Straight 変換 +// ======================================================================== + +// 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 + } +} + +// 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; + } +} + +// ------------------------------------------------------------------------ +// Grayscale8 フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +// ======================================================================== +// ビット操作ヘルパー関数(bit-packed Grayscale/Index共用) +// ======================================================================== + +namespace bit_packed_detail { + +// ======================================================================== +// unpackIndexBits: packed bytes -> 8bit value array +// ======================================================================== + +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; + } + } + + ++byteIdx; + pixelIdx = 0; // 次のバイトからは先頭から読む + } +} + +// ======================================================================== +// packIndexBits: 8bit value array -> packed bytes +// ======================================================================== + +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)); + } + } + dst[i] = b; + src += PixelsPerByte; + pixelCount -= PixelsPerByte; + } +} + +// ======================================================================== +// ビット単位アクセスヘルパー(LovyanGFXスタイル) +// ======================================================================== + +// 指定座標のピクセルを 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; + } +} + +} // namespace bit_packed_detail + +// ======================================================================== +// GrayscaleN: bit-packed Grayscale <-> RGBA8_Straight 変換 +// ======================================================================== + +// GrayscaleN -> RGBA8_Straight(bit-packed -> RGBA8) +// 末尾詰め方式: +// 出力バッファ(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); +} + +// 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; + } + + // パック + bit_packed_detail::packIndexBits(dstPtr, grayBuf, chunk); + + srcPtr += chunk * 4; + dstPtr += (chunk + MaxPixelsPerByte - 1) / MaxPixelsPerByte; + remaining -= chunk; + } +} + +// ------------------------------------------------------------------------ +// Bit-packed Grayscale Formats フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +// Forward declarations for sibling references +extern const PixelFormatDescriptor Grayscale1_LSB; +extern const PixelFormatDescriptor Grayscale2_LSB; +extern const PixelFormatDescriptor Grayscale4_LSB; + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 1, // bitsPerPixel + 1, // bytesPerPixel + 8, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + false, // isIndexed +}; + +const PixelFormatDescriptor Grayscale1_LSB = { + "Grayscale1_LSB", + grayscaleN_toStraight<1, BitOrder::LSBFirst>, + grayscaleN_fromStraight<1, BitOrder::LSBFirst>, + nullptr, + nullptr, + &Grayscale1_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<1, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 0, + 1, + 1, + 8, + 1, + 1, + false, + false, +}; + +const PixelFormatDescriptor Grayscale2_MSB = { + "Grayscale2_MSB", + grayscaleN_toStraight<2, BitOrder::MSBFirst>, + grayscaleN_fromStraight<2, BitOrder::MSBFirst>, + nullptr, + nullptr, + &Grayscale2_LSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<2, BitOrder::MSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::MSBFirst>, + BitOrder::MSBFirst, + ByteOrder::Native, + 0, + 2, + 1, + 4, + 1, + 1, + false, + false, +}; + +const PixelFormatDescriptor Grayscale2_LSB = { + "Grayscale2_LSB", + grayscaleN_toStraight<2, BitOrder::LSBFirst>, + grayscaleN_fromStraight<2, BitOrder::LSBFirst>, + nullptr, + nullptr, + &Grayscale2_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<2, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 0, + 2, + 1, + 4, + 1, + 1, + false, + false, +}; + +const PixelFormatDescriptor Grayscale4_MSB = { + "Grayscale4_MSB", + grayscaleN_toStraight<4, BitOrder::MSBFirst>, + grayscaleN_fromStraight<4, BitOrder::MSBFirst>, + nullptr, + nullptr, + &Grayscale4_LSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<4, BitOrder::MSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::MSBFirst>, + BitOrder::MSBFirst, + ByteOrder::Native, + 0, + 4, + 1, + 2, + 1, + 1, + false, + false, +}; + +const PixelFormatDescriptor Grayscale4_LSB = { + "Grayscale4_LSB", + grayscaleN_toStraight<4, BitOrder::LSBFirst>, + grayscaleN_fromStraight<4, BitOrder::LSBFirst>, + nullptr, + nullptr, + &Grayscale4_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<4, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 0, + 4, + 1, + 2, + 1, + 1, + false, + false, +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/pixel_format/index.inl b/impl/fleximg/image/pixel_format/index.inl new file mode 100644 index 0000000..548ec75 --- /dev/null +++ b/impl/fleximg/image/pixel_format/index.inl @@ -0,0 +1,287 @@ +/** + * @file index.inl + * @brief Index ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/index.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// 共通パレットLUT関数(__restrict__ なし、in-place安全) +// ======================================================================== +// +// インデックス値(uint8_t配列)をパレットフォーマットのピクセルに展開する。 +// index8_expandIndex / indexN_expandIndex 双方から呼ばれる共通実装。 +// __restrict__ なしのため、末尾詰め方式のin-place展開にも対応。 +// +// 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)); + } + } +} + +// ======================================================================== +// 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); +} + +// ======================================================================== +// Index8: RGBA8_Straight -> Index8 変換 +// ======================================================================== +// +// RGBカラーからインデックス値への変換。 +// パレットなし時はBT.601輝度計算にフォールバック(grayscale8_fromStraightに委譲)。 +// 将来的にパレットへの最近傍色マッチングに拡張予定。 +// + +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); +} + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +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で実施) + BitOrder::MSBFirst, + ByteOrder::Native, + 256, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + true, // isIndexed +}; + +// ======================================================================== +// Bit-packed Index Formats (Index1/2/4 MSB/LSB) +// ======================================================================== + +// 変換関数: expandIndex (パレット展開) +// 末尾詰め方式: 出力バッファ末尾に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); +} + +// 変換関数: 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); +} + +// ------------------------------------------------------------------------ +// Bit-packed Index Formats フォーマット定義 +// ------------------------------------------------------------------------ + +// Forward declarations for sibling references +extern const PixelFormatDescriptor Index1_LSB; +extern const PixelFormatDescriptor Index2_LSB; +extern const PixelFormatDescriptor Index4_LSB; + +const PixelFormatDescriptor Index1_MSB = { + "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 + BitOrder::MSBFirst, + ByteOrder::Native, + 2, // maxPaletteSize + 1, // bitsPerPixel + 1, // bytesPerPixel + 8, // pixelsPerUnit + 1, // bytesPerUnit + 1, // channelCount + false, // hasAlpha + true, // isIndexed +}; + +const PixelFormatDescriptor Index1_LSB = { + "Index1_LSB", + grayscaleN_toStraight<1, BitOrder::LSBFirst>, + indexN_fromStraight<1, BitOrder::LSBFirst>, + indexN_expandIndex<1, BitOrder::LSBFirst>, + nullptr, + &Index1_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<1, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 2, // maxPaletteSize + 1, + 1, + 8, + 1, + 1, + false, + true, +}; + +const PixelFormatDescriptor Index2_MSB = { + "Index2_MSB", + grayscaleN_toStraight<2, BitOrder::MSBFirst>, + indexN_fromStraight<2, BitOrder::MSBFirst>, + indexN_expandIndex<2, BitOrder::MSBFirst>, + nullptr, + &Index2_LSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<2, BitOrder::MSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::MSBFirst>, + BitOrder::MSBFirst, + ByteOrder::Native, + 4, // maxPaletteSize + 2, + 1, + 4, + 1, + 1, + false, + true, +}; + +const PixelFormatDescriptor Index2_LSB = { + "Index2_LSB", + grayscaleN_toStraight<2, BitOrder::LSBFirst>, + indexN_fromStraight<2, BitOrder::LSBFirst>, + indexN_expandIndex<2, BitOrder::LSBFirst>, + nullptr, + &Index2_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<2, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 4, // maxPaletteSize + 2, + 1, + 4, + 1, + 1, + false, + true, +}; + +const PixelFormatDescriptor Index4_MSB = { + "Index4_MSB", + grayscaleN_toStraight<4, BitOrder::MSBFirst>, + indexN_fromStraight<4, BitOrder::MSBFirst>, + indexN_expandIndex<4, BitOrder::MSBFirst>, + nullptr, + &Index4_LSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<4, BitOrder::MSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::MSBFirst>, + BitOrder::MSBFirst, + ByteOrder::Native, + 16, // maxPaletteSize + 4, + 1, + 2, + 1, + 1, + false, + true, +}; + +const PixelFormatDescriptor Index4_LSB = { + "Index4_LSB", + grayscaleN_toStraight<4, BitOrder::LSBFirst>, + indexN_fromStraight<4, BitOrder::LSBFirst>, + indexN_expandIndex<4, BitOrder::LSBFirst>, + nullptr, + &Index4_MSB, + nullptr, + pixel_format::detail::copyRowDDA_Bit<4, BitOrder::LSBFirst>, + pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::LSBFirst>, + BitOrder::LSBFirst, + ByteOrder::Native, + 16, // maxPaletteSize + 4, + 1, + 2, + 1, + 1, + false, + true, +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/pixel_format/rgb332.inl b/impl/fleximg/image/pixel_format/rgb332.inl new file mode 100644 index 0000000..b3acd04 --- /dev/null +++ b/impl/fleximg/image/pixel_format/rgb332.inl @@ -0,0 +1,112 @@ +/** + * @file rgb332.inl + * @brief RGB332 ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/rgb332.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// RGB332: 8bit RGB (3-3-2) +// ======================================================================== + +// RGB332 -> RGBA8 変換ルックアップテーブル +// RGB332の256通りの値に対して、RGBA8値を事前計算 +// 各エントリ: uint32_t (リトルエンディアン: R8 | G8<<8 | B8<<16 | A8<<24) +// 32bitロード/ストアで効率的に変換可能 +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) + +// RGB332 -> RGBA8 変換テーブル (256 x 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(0xc0), RGB332_ROW(0xd0), RGB332_ROW(0xe0), RGB332_ROW(0xf0)}; + +#undef RGB332_ENTRY +#undef RGB332_ROW + +} // 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); +} + +// 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; + } +} +#undef RGBA8_TO_RGB332 + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 8, // bitsPerPixel + 1, // bytesPerPixel + 1, // pixelsPerUnit + 1, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/pixel_format/rgb565.inl b/impl/fleximg/image/pixel_format/rgb565.inl new file mode 100644 index 0000000..1af1e4e --- /dev/null +++ b/impl/fleximg/image/pixel_format/rgb565.inl @@ -0,0 +1,324 @@ +/** + * @file rgb565.inl + * @brief RGB565 ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/rgb565.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// RGB565_LE: 16bit RGB (Little Endian) +// ======================================================================== + +// RGB565 -> RGB8 変換ルックアップテーブル +// RGB565の16bit値を上位バイトと下位バイトに分けて処理 +// +// RGB565構造 (16bit): RRRRR GGGGGG BBBBB +// high_byte: RRRRRGGG (R5全部 + G6上位3bit) +// low_byte: GGGBBBBB (G6下位3bit + B5全部) +// +// G8の分離計算: +// G8 = (G6 << 2) | (G6 >> 4) +// = (high_G3 << 5) + (high_G3 >> 1) + (low_G3 << 2) + (low_G3 >> 4) +// ※ low_G3 >> 4 は low_G3 が 0-7 なので常に 0 +// +// テーブル構成 (uint16_t配列、リトルエンディアン前提): +// high_table[high_byte] = (G_high << 8) | R8 where G_high = (high_G3 << 5) + +// (high_G3 >> 1) low_table[low_byte] = (G_low << 8) | B8 where G_low = +// low_G3 << 2 +// +// 両テーブルとも上位バイトに緑成分を配置 +// +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))) + +// 下位バイト用エントリ: 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) + +// RGB565上位バイト用テーブル (256 x 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下位バイト用テーブル (256 x 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)}; + +#undef RGB565_HIGH_ENTRY +#undef RGB565_LOW_ENTRY +#undef RGB565_HIGH_ROW +#undef RGB565_LOW_ROW + +} // 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; + } +} +#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; + } +} + +// ======================================================================== +// RGB565_BE: 16bit RGB (Big Endian) +// ======================================================================== + +// 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; + } +} +#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; + } +} + +// ======================================================================== +// 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)); + } +} + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +// Forward declaration for sibling reference +extern const PixelFormatDescriptor RGB565_BE; + +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 + BitOrder::MSBFirst, + ByteOrder::LittleEndian, + 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 + BitOrder::MSBFirst, + ByteOrder::BigEndian, + 0, // maxPaletteSize + 16, // bitsPerPixel + 2, // bytesPerPixel + 1, // pixelsPerUnit + 2, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/pixel_format/rgb888.inl b/impl/fleximg/image/pixel_format/rgb888.inl new file mode 100644 index 0000000..6d1402a --- /dev/null +++ b/impl/fleximg/image/pixel_format/rgb888.inl @@ -0,0 +1,245 @@ +/** + * @file rgb888.inl + * @brief RGB888/BGR888 ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/rgb888.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +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_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_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; + } +} + +// ======================================================================== +// エンディアン・バイトスワップ関数 +// ======================================================================== + +// 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]; + } +} + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +// Forward declarations for sibling references +extern const PixelFormatDescriptor BGR888; + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 24, // bitsPerPixel + 3, // bytesPerPixel + 1, // pixelsPerUnit + 3, // bytesPerUnit + 3, // channelCount + false, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/pixel_format/rgba8_straight.inl b/impl/fleximg/image/pixel_format/rgba8_straight.inl new file mode 100644 index 0000000..6895f1b --- /dev/null +++ b/impl/fleximg/image/pixel_format/rgba8_straight.inl @@ -0,0 +1,266 @@ +/** + * @file rgba8_straight.inl + * @brief RGBA8_Straight ピクセルフォーマット 実装 + * @see src/fleximg/image/pixel_format/rgba8_straight.h + */ + +#include "../../../../src/fleximg/core/format_metrics.h" + +namespace FLEXIMG_NAMESPACE { + +// ======================================================================== +// RGBA8_Straight 変換関数 +// 標準フォーマット: RGBA8_Straight(8bit RGBA、ストレートアルファ) +// ======================================================================== + +// 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_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合成(背面への合成) +// +// under合成の数式: +// resultA = dstA + srcA * (1 - dstA/255) +// resultColor = (dstColor * dstA + srcColor * srcA * (1 - dstA/255)) / +// resultA +// +// 処理パターン: +// - dstA == 255(不透明): スキップ(背面は見えない) +// - dstA == 0(透明): srcをコピー +// - srcA == 0(透明): スキップ(合成対象なし) +// - それ以外: ブレンド計算 +// +// 最適化手法: +// 1. gotoラベル方式のディスパッチ(分岐予測しやすい) +// 2. 4ピクセル単位の連続領域高速スキップ/コピー +// 3. 正規化重み方式によるブレンド計算の効率化: +// - 重みを合計256に正規化し、シフトで除算を代替 +// - 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; +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(正確な計算) + + 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; + + // 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 = static_cast(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; +} + + // ======================================================================== + // 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 = static_cast(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; +} + + // ======================================================================== + // 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 = static_cast(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; +} +} + +// ------------------------------------------------------------------------ +// フォーマット定義 +// ------------------------------------------------------------------------ + +namespace BuiltinFormats { + +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 + BitOrder::MSBFirst, + ByteOrder::Native, + 0, // maxPaletteSize + 32, // bitsPerPixel + 4, // bytesPerPixel + 1, // pixelsPerUnit + 4, // bytesPerUnit + 4, // channelCount + true, // hasAlpha + false, // isIndexed +}; + +} // namespace BuiltinFormats + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/image/viewport.inl b/impl/fleximg/image/viewport.inl new file mode 100644 index 0000000..d71b45d --- /dev/null +++ b/impl/fleximg/image/viewport.inl @@ -0,0 +1,397 @@ +/** + * @file viewport.inl + * @brief ViewPort 操作関数の実装 + * @see src/fleximg/image/viewport.h + */ + +#include "../../../src/fleximg/operations/transform.h" +#include +#include + +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 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 { + +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); + } +} + +// ============================================================================ +// バイリニア補間関数(RGBA8888固定) +// ============================================================================ +// +// copyQuadDDAで抽出した4ピクセルデータからバイリニア補間を実行する。 +// 入力: quadPixels = [p00,p10,p01,p11] × count(各ピクセル4bytes、RGBA8888) +// 境界外ピクセルは呼び出し前にゼロ埋めされていること +// 出力: 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; + } +} + +// ============================================================================ +// バイリニア補間関数(1チャンネル固定: Alpha8/Grayscale8用) +// ============================================================================ +// +// copyQuadDDAで抽出した4ピクセルデータからバイリニア補間を実行する。 +// 入力: quadPixels = [p00,p10,p01,p11] × count(各ピクセル1byte) +// 境界外ピクセルは呼び出し前にゼロ埋めされていること +// 出力: 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); + } +} + +// ============================================================================ +// copyRowDDABilinear +// ============================================================================ +// +// 処理フロー(チャンクループ): +// a. copyQuadDDA: 4ピクセル抽出 + edgeFlags生成 +// b. convertFormat: フォーマット変換(RGBA8_Straight以外の場合) +// c. edgeFlags適用: 境界ピクセルのアルファを0化 +// 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; + + // 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パス: 通常のマルチチャンネルフォーマット + // ======================================================================== + + // チャンク処理用定数 + 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, static_cast(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; + } + } + + // バイリニア補間 + bilinearBlend_RGBA8888(dstPtr, quadRGBA, weightsXY, chunk); + + // 次のチャンクへ + dstPtr += chunk; + param.srcX += incrX * chunk; + param.srcY += incrY * 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; + + 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); + } +} + +} // namespace view_ops +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/affine_node.inl b/impl/fleximg/nodes/affine_node.inl new file mode 100644 index 0000000..2434ab7 --- /dev/null +++ b/impl/fleximg/nodes/affine_node.inl @@ -0,0 +1,82 @@ +/** + * @file affine_node.inl + * @brief AffineNode 実装 + * @see src/fleximg/nodes/affine_node.h + */ + +namespace FLEXIMG_NAMESPACE { + +// ============================================================================ +// AffineNode - Template Method フック実装 +// ============================================================================ + +// 複数の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; +} + +// 複数の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; +} + +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); + } +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/composite_node.inl b/impl/fleximg/nodes/composite_node.inl new file mode 100644 index 0000000..5b70d9f --- /dev/null +++ b/impl/fleximg/nodes/composite_node.inl @@ -0,0 +1,204 @@ +/** + * @file composite_node.inl + * @brief CompositeNode 実装 + * @see src/fleximg/nodes/composite_node.h + */ + +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; + } + } + + // 全上流へ伝播し、結果をマージ(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; + } + } 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; +} + +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; +} + +// onPullProcess: 複数の上流から画像を取得してunder合成 +// 単一バッファ事前確保方式: +// - 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; + } + + FLEXIMG_METRICS_SCOPE(NodeType::Composite); + + // 上流のバッファをblendFrom + if (input.hasBuffer()) { + compositeBuf->blendFrom(input.buffer()); + } + + context_->releaseResponse(input); + } + + resp.origin = compositeOrigin; + return resp; +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/distributor_node.inl b/impl/fleximg/nodes/distributor_node.inl new file mode 100644 index 0000000..157eada --- /dev/null +++ b/impl/fleximg/nodes/distributor_node.inl @@ -0,0 +1,161 @@ +/** + * @file distributor_node.inl + * @brief DistributorNode 実装 + * @see src/fleximg/nodes/distributor_node.h + */ + +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; + } + } + + // 全下流へ伝播し、結果をマージ(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; + } + } else { + // 下流がない場合はサイズ0を返す + // width/height/originはデフォルト値(0)のまま + } + + return merged; +} + +void DistributorNode::onPushFinalize() +{ + // 全下流へ伝播 + int numOutputs = outputCount(); + for (int i = 0; i < numOutputs; ++i) { + Node *downstream = downstreamNode(i); + if (downstream) { + downstream->pushFinalize(); + } + } + finalize(); +} + +void DistributorNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + // プッシュ型単一入力: 無効なら処理終了 + if (!input.isValid()) { + return; + } + + // バッファ準備 + consolidateIfNeeded(input); + + FLEXIMG_METRICS_SCOPE(NodeType::Distributor); + + int numOutputs = outputCount(); + int validOutputs = 0; + + // 接続されている出力を数える + for (int i = 0; i < numOutputs; ++i) { + if (downstreamNode(i)) { + ++validOutputs; + } + } + + 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); + } + } +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/filter_node_base.inl b/impl/fleximg/nodes/filter_node_base.inl new file mode 100644 index 0000000..c11b78b --- /dev/null +++ b/impl/fleximg/nodes/filter_node_base.inl @@ -0,0 +1,66 @@ +/** + * @file filter_node_base.inl + * @brief FilterNodeBase 実装 + * @see src/fleximg/nodes/filter_node_base.h + */ + +namespace FLEXIMG_NAMESPACE { + +// ============================================================================ +// FilterNodeBase - Template Method フック実装 +// ============================================================================ + +RenderResponse &FilterNodeBase::onPullProcess(const RenderRequest &request) +{ + Node *upstream = upstreamNode(0); + if (!upstream) return makeEmptyResponse(request.origin); + + int_fast16_t margin = static_cast(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); +#endif + + RenderResponse &input = upstream->pullProcess(inputReq); + if (!input.isValid()) return input; + + // process() を呼ぶ(Node基底クラスの設計に沿う) + return process(input, request); +} + +// ============================================================================ +// FilterNodeBase - process() 共通実装 +// ============================================================================ +// +// スキャンライン必須仕様(height=1)前提の共通処理: +// 1. RGBA8_Straight形式に変換 +// 2. ラインフィルタ関数を適用 +// 3. パフォーマンス計測(デバッグビルド時) +// + +RenderResponse &FilterNodeBase::process(RenderResponse &input, const RenderRequest &request) +{ + (void)request; // スキャンライン必須仕様では未使用 + FLEXIMG_METRICS_SCOPE(nodeTypeForMetrics()); + + // フォーマット変換を実行(メトリクス記録付き) + consolidateIfNeeded(input, PixelFormatIDs::RGBA8_Straight); + + // 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_); + + // inputをそのまま返す(借用元への変更が反映される) + return input; +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/horizontal_blur_node.inl b/impl/fleximg/nodes/horizontal_blur_node.inl new file mode 100644 index 0000000..738abf9 --- /dev/null +++ b/impl/fleximg/nodes/horizontal_blur_node.inl @@ -0,0 +1,270 @@ +/** + * @file horizontal_blur_node.inl + * @brief HorizontalBlurNode 実装 + * @see src/fleximg/nodes/horizontal_blur_node.h + */ + +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; + } + + // 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; +} + +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; +#endif + + // RGBA8_Straightに変換 + ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), PixelFormatIDs::RGBA8_Straight); + + // 上流から返された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); + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + 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}); +} + +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); + } + + // 下流に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); + } +} + +// ============================================================================ +// HorizontalBlurNode - private ヘルパーメソッド実装 +// ============================================================================ + +// 水平方向ブラー処理(共通) +// 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; + } + } + 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); + } +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/matte_node.inl b/impl/fleximg/nodes/matte_node.inl new file mode 100644 index 0000000..a6b5362 --- /dev/null +++ b/impl/fleximg/nodes/matte_node.inl @@ -0,0 +1,752 @@ +/** + * @file matte_node.inl + * @brief MatteNode 実装 + * @see src/fleximg/nodes/matte_node.h + */ + +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; + } + } + } + + 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; +} + +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; + } + } + + rangeCache_.unionRange = (startX < endX) ? DataRange{startX, endX} : DataRange{}; + rangeCache_.origin = request.origin; + rangeCache_.valid = true; + + return rangeCache_.unionRange; +} + +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); +} + +// ============================================================================ +// MatteNode - onPullProcess実装(最適化版) +// ============================================================================ +// +// 処理フロー: +// 1. mask有効範囲の確定(早期リターン) +// - maskデータなし / 取得失敗 / 全面0 → bg直接返却(変換なし) +// 2. bg取得・出力領域計算 +// 3. bg戦略決定・出力バッファ作成 +// 4. fg取得(mask有効範囲のみ) +// 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; + } + + // fg∪bgが空 → マスク値に関わらず出力は透明 + if (fgBgStart >= fgBgEnd) { + rangeCache_.valid = false; + return makeEmptyResponse(request.origin); + } + + // 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; + } + } + + RenderResponse &maskResult = maskNode->pullProcess(maskRequest); + if (!maskResult.isValid()) goto fallback_bg; + + // バッファ準備 + consolidateIfNeeded(maskResult); + + // Alpha8に変換 + if (maskResult.buffer().formatID() != PixelFormatIDs::Alpha8) { + maskResult.convertFormat(PixelFormatIDs::Alpha8); + } + + // 全面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されたビューを再取得 + } + + // ======================================================================== + // Step 2: bg取得・出力領域計算 + // ======================================================================== + + RenderResponse *bgResultPtr = nullptr; + if (rangeCache_.bgRange.hasData() && bgNode) { + RenderResponse &bgResult = bgNode->pullProcess(request); + if (bgResult.isValid()) { + // バッファ準備 + consolidateIfNeeded(bgResult); + bgResultPtr = &bgResult; + } + } + + // 出力領域計算(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; + } + + auto unionWidth = static_cast(from_fixed(unionMaxX - unionMinX)); + auto unionHeight = static_cast(from_fixed(unionMaxY - unionMinY)); + + // ======================================================================== + // Step 3: 出力バッファ作成(ゼロクリア)+ bgコピー + // ======================================================================== + + FLEXIMG_METRICS_SCOPE(NodeType::Matte); + + 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()); +#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, static_cast(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); + } + fgResultPtr = &fgResult; + } + } + + // ======================================================================== + // Step 5: 合成 + // ======================================================================== + + // 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); + + applyMatteOverlay(outputBuf, unionWidth, fgView, maskInputView); + + // キャッシュ無効化 + rangeCache_.valid = false; + + return makeResponse(std::move(outputBuf), Point{unionMinX, unionMinY}); + } + + // bgフォールバック: mask無効時はbgを直接返却 +fallback_bg: + 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バイト単位(ポインタベース) + { + 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; + } + } + + // 全面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; + } + + // 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; + } + } + + outRightSkip = rightSkip; + return maskWidth - leftSkip - rightSkip; +} + +// ============================================================================ +// MatteNode - 合成処理実装 +// ============================================================================ + +// ---------------------------------------------------------------------------- +// processRowNoFg: fgなし領域の行処理 +// - alpha=0: スキップ(出力には既にbgがある) +// - 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; + + uint_fast8_t alpha = *m; + + if (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; + +blend: + // ブレンドループ: 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; + // 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; + +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 (alpha == 0) goto handle_alpha_0; + if (alpha == 255) goto handle_alpha_255; + goto blend; +} + +// ---------------------------------------------------------------------------- +// processRowWithFg: fg領域の行処理 +// - alpha=0: スキップ(出力には既にbgがある) +// - 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; + + uint_fast8_t alpha = *m; + + 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 { + ++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; + } + } + } + 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 (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)); + } + + // 中央領域: 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)); + } + } +} + +#if defined(BENCH_M5STACK) || defined(BENCH_NATIVE) +// ============================================================================ +// 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::benchProcessRowNoFg(uint8_t *d, const uint8_t *m, int pixelCount) +{ + processRowNoFg(d, m, static_cast(pixelCount)); +} +#endif + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/ninepatch_source_node.inl b/impl/fleximg/nodes/ninepatch_source_node.inl new file mode 100644 index 0000000..b125123 --- /dev/null +++ b/impl/fleximg/nodes/ninepatch_source_node.inl @@ -0,0 +1,359 @@ +/** + * @file ninepatch_source_node.inl + * @brief NinePatchSourceNode 実装 + * @see src/fleximg/nodes/ninepatch_source_node.h + */ + +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_; + } 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; +} + +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}); +} + +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::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(int_fixed(0), int_fixed(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 diff --git a/impl/fleximg/nodes/renderer_node.inl b/impl/fleximg/nodes/renderer_node.inl new file mode 100644 index 0000000..643255c --- /dev/null +++ b/impl/fleximg/nodes/renderer_node.inl @@ -0,0 +1,228 @@ +/** + * @file renderer_node.inl + * @brief RendererNode 実装 + * @see src/fleximg/nodes/renderer_node.h + */ + +namespace FLEXIMG_NAMESPACE { + +// ============================================================================ +// RendererNode - 実行API実装 +// ============================================================================ + +PrepareStatus RendererNode::execPrepare() +{ +#ifdef FLEXIMG_DEBUG_PERF_METRICS + // メトリクスをリセット + 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; +} + +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可視化処理 +// - getDataRange()の範囲外: マゼンタ(データがないはずの領域) +// - 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]; + } + } + } + + // バッファ境界マーカーを追加(半透明オレンジ) + 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); + } + } + } + + // 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; +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/sink_node.inl b/impl/fleximg/nodes/sink_node.inl new file mode 100644 index 0000000..d398461 --- /dev/null +++ b/impl/fleximg/nodes/sink_node.inl @@ -0,0 +1,189 @@ +/** + * @file sink_node.inl + * @brief SinkNode 実装 + * @see src/fleximg/nodes/sink_node.h + */ + +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; + } 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 { + 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; +} + +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::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 diff --git a/impl/fleximg/nodes/source_node.inl b/impl/fleximg/nodes/source_node.inl new file mode 100644 index 0000000..7f4e5aa --- /dev/null +++ b/impl/fleximg/nodes/source_node.inl @@ -0,0 +1,432 @@ +/** + * @file source_node.inl + * @brief SourceNode 実装 + * @see src/fleximg/nodes/source_node.h + */ + +namespace FLEXIMG_NAMESPACE { + +// ============================================================================ +// SourceNode - Template Method フック実装 +// ============================================================================ + +PrepareResponse SourceNode::onPullPrepare(const PrepareRequest &request) +{ + // 下流からの希望フォーマットを保存(将来のフォーマット最適化用) + preferredFormat_ = request.preferredFormat; + + // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) + dataRangeCache_.invalidate(); + + // 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_; // 無変換時は単位行列 + } + + // 逆行列とピクセル中心オフセットを計算 + 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; + } + + // 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; +} + +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); +} + +// ============================================================================ +// SourceNode - private ヘルパーメソッド実装 +// ============================================================================ + +// スキャンライン有効範囲を計算(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; +} + +// 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; +} + +// アフィン変換付きプル処理(スキャンライン専用) +// 前提: 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_fast16_t validWidth = static_cast(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; + } else { + outFormat = source_.formatID; + } + } + ImageBuffer *output = resp.createBuffer(validWidth, 1, outFormat, InitPolicy::Uninitialized); + + if (!output) { + return resp; // バッファ作成失敗時は空のResponseを返す + } + + // バッファにワールド座標originを設定(makeResponseを使わないパス) + output->setOrigin(adjustedOrigin); + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + 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; + + 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); + } + } + + // パレット情報を出力ImageBufferに設定 + if (palette_) { + output->setPalette(palette_); + } + // カラーキー情報を出力ImageBufferに設定 + if (colorKeyRGBA8_ != colorKeyReplace_) { + output->auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; + output->auxInfo().colorKeyReplace = colorKeyReplace_; + } + + return resp; +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/nodes/vertical_blur_node.inl b/impl/fleximg/nodes/vertical_blur_node.inl new file mode 100644 index 0000000..f6ebae6 --- /dev/null +++ b/impl/fleximg/nodes/vertical_blur_node.inl @@ -0,0 +1,729 @@ +/** + * @file vertical_blur_node.inl + * @brief VerticalBlurNode 実装 + * @see src/fleximg/nodes/vertical_blur_node.h + */ + +namespace FLEXIMG_NAMESPACE { + +// ======================================== +// 準備・終了処理 +// ======================================== + +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; + + // パイプライン方式でキャッシュを初期化(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_); +#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; +} + +// ======================================== +// 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}; +} + +// ======================================== +// 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; + } + + // スクリーン情報を保存(prepare()代わり) + screenWidth_ = request.width; + screenHeight_ = request.height; + screenOrigin_ = request.origin; + + // 上流の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); + +#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_); +#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); + + 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; +} + +void VerticalBlurNode::onPushProcess(RenderResponse &input, const RenderRequest &request) +{ + // radius=0の場合はスルー + if (radius_ == 0) { + Node *downstream = downstreamNode(0); + if (downstream) { + downstream->pushProcess(input, request); + } + 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); + + // 古い行を列合計から減算 + if (stage0.pushInputY >= ks) { + 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); + } + stage0.rowOriginX[static_cast(slot0)] = inputOrigin.x; + + // 新しい行を列合計に加算 + updateStageColSum(stage0, slot0, true); + + lastInputOriginY_ = inputOrigin.y; + stage0.pushInputY++; + + // 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(); + + // 残りの行を出力(下端はゼロパディング扱い) + 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); + + // radius=0の場合は処理をスキップしてスルー出力 + if (radius_ == 0) { + return upstream->pullProcess(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); + +#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; +#endif + + ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); + +#ifdef FLEXIMG_DEBUG_PERF_METRICS + 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; + } + } + + // 出力の origin を計算(バッファ左上のワールド座標) + Point outputOrigin; + outputOrigin.x = interLeft; + outputOrigin.y = request.origin.y; + + 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(); + + // このステージへの最初の呼び出し時、currentYを調整してキャッシュを完全に充填 + // newY - kernelSize() + // から開始することで、kernelSize()回のループでキャッシュが充填される + if (!stage.cacheReady) { + stage.currentY = newY - ks; + stage.cacheReady = true; + } + + if (stage.currentY == newY) return; + + 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; + + // 古い行を列合計から減算 + updateStageColSum(stage, slot, false); + + // 新しい行を取得してキャッシュに格納 + 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; + } +} + +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 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}; +} + +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::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); + } +} + +// ======================================== +// 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); + } + + // 新しい行をキャッシュに格納 + 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; + } + } + + // 最終ステージが出力可能になったら下流に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; + } + } + + 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); + + RenderRequest outReq; + outReq.width = static_cast(cacheWidth_); + outReq.height = 1; + outReq.origin.x = originX; + outReq.origin.y = originY; + + pushOutputY_++; + + 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); + } +} + +} // namespace FLEXIMG_NAMESPACE diff --git a/impl/fleximg/operations/filters.inl b/impl/fleximg/operations/filters.inl new file mode 100644 index 0000000..1f75f1b --- /dev/null +++ b/impl/fleximg/operations/filters.inl @@ -0,0 +1,64 @@ +/** + * @file filters.inl + * @brief ラインフィルタ関数の実装 + * @see src/fleximg/operations/filters.h + */ + +#include +#include + +namespace FLEXIMG_NAMESPACE { +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))); + } + // 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); + + 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 diff --git a/platformio.ini b/platformio.ini index 0132126..583f46a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -109,7 +109,7 @@ build_src_filter = -<*> + ; ============================================================================= [env:bench_native] extends = common_native -build_src_filter = -<*> +<../examples/bench/src/*> +build_src_filter = -<*> + +<../examples/bench/src/*> build_flags = ${common_native.build_flags} -DBENCH_NATIVE=1 @@ -117,7 +117,7 @@ build_flags = [env:bench_m5stack_core2] extends = common_m5stack board = m5stack-core2 -build_src_filter = -<*> +<../examples/bench/src/*> +build_src_filter = -<*> + +<../examples/bench/src/*> build_flags = ${common_m5stack.build_flags} -DBENCH_M5STACK=1 @@ -125,7 +125,7 @@ build_flags = [env:bench_m5stack_cores3] extends = common_m5stack board = m5stack-cores3 -build_src_filter = -<*> +<../examples/bench/src/*> +build_src_filter = -<*> + +<../examples/bench/src/*> build_flags = ${common_m5stack.build_flags} -DBENCH_M5STACK=1 @@ -135,12 +135,12 @@ build_flags = ; ============================================================================= [env:basic_native] extends = common_native_m5 -build_src_filter = -<*> +<../examples/m5stack_basic/src/*> +build_src_filter = -<*> + +<../examples/m5stack_basic/src/*> [env:basic_m5stack_core2] extends = common_m5stack board = m5stack-core2 -build_src_filter = -<*> +<../examples/m5stack_basic/src/*> +build_src_filter = -<*> + +<../examples/m5stack_basic/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORE2 @@ -148,7 +148,7 @@ build_flags = [env:basic_m5stack_cores3] extends = common_m5stack board = m5stack-cores3 -build_src_filter = -<*> +<../examples/m5stack_basic/src/*> +build_src_filter = -<*> + +<../examples/m5stack_basic/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORES3 @@ -158,12 +158,12 @@ build_flags = ; ============================================================================= [env:matte_native] extends = common_native_m5 -build_src_filter = -<*> +<../examples/m5stack_matte/src/*> +build_src_filter = -<*> + +<../examples/m5stack_matte/src/*> [env:matte_m5stack_core2] extends = common_m5stack board = m5stack-core2 -build_src_filter = -<*> +<../examples/m5stack_matte/src/*> +build_src_filter = -<*> + +<../examples/m5stack_matte/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORE2 @@ -171,7 +171,7 @@ build_flags = [env:matte_m5stack_cores3] extends = common_m5stack board = m5stack-cores3 -build_src_filter = -<*> +<../examples/m5stack_matte/src/*> +build_src_filter = -<*> + +<../examples/m5stack_matte/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORES3 @@ -181,12 +181,12 @@ build_flags = ; ============================================================================= [env:hos_native] extends = common_native_m5 -build_src_filter = -<*> +<../examples/m5stack_hos/src/*> +build_src_filter = -<*> + +<../examples/m5stack_hos/src/*> [env:hos_m5stack_core2] extends = common_m5stack board = m5stack-core2 -build_src_filter = -<*> +<../examples/m5stack_hos/src/*> +build_src_filter = -<*> + +<../examples/m5stack_hos/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORE2 @@ -194,7 +194,7 @@ build_flags = [env:hos_m5stack_cores3] extends = common_m5stack board = m5stack-cores3 -build_src_filter = -<*> +<../examples/m5stack_hos/src/*> +build_src_filter = -<*> + +<../examples/m5stack_hos/src/*> build_flags = ${common_m5stack.build_flags} -DARDUINO_M5STACK_CORES3 diff --git a/src/fleximg/core/common.h b/src/fleximg/core/common.h index e8ee602..9c4ec17 100644 --- a/src/fleximg/core/common.h +++ b/src/fleximg/core/common.h @@ -20,9 +20,10 @@ // デバッグログマクロ // ======================================================================== // -// FLEXIMG_DEBUG_LOG(fmt, ...): デバッグメッセージ出力 + flush -// - ARDUINO環境: printf + fflush + vTaskDelay(1) -// - その他: printf + fflush +// FLEXIMG_DEBUG_LOG(fmt, ...): デバッグメッセージ出力 + flush + delay +// - FreeRTOS環境(ESP-IDF等): printf + fflush + vTaskDelay(1) +// - Arduino環境(FreeRTOSなし): printf + fflush + delay(1) +// - その他(PC/WASM): printf + fflush // // FLEXIMG_DEBUG_WARN(fmt, ...): 警告出力(デバッグビルドのみ有効) // @@ -30,20 +31,27 @@ // FLEXIMG_DEBUG_LOG は ASSERT/REQUIRE から使用するため常に定義。 // -#ifdef ARDUINO -#define FLEXIMG_DEBUG_LOG(fmt, ...) \ - do { \ - printf(fmt "\n", __VA_ARGS__); \ - fflush(stdout); \ - vTaskDelay(1); \ - } while (0) +// プラットフォーム別のデバッグ用ディレイ +#if __has_include() +// FreeRTOS 環境(ESP-IDF / ESP32 + Arduino 等) +#include +#include +#define FLEXIMG_DEBUG_DELAY_() vTaskDelay(1) +#elif defined(ARDUINO) +// FreeRTOS なし Arduino(AVR 等) +#include +#define FLEXIMG_DEBUG_DELAY_() delay(1) #else +// PC / WASM +#define FLEXIMG_DEBUG_DELAY_() ((void)0) +#endif + #define FLEXIMG_DEBUG_LOG(fmt, ...) \ do { \ printf(fmt "\n", __VA_ARGS__); \ fflush(stdout); \ + FLEXIMG_DEBUG_DELAY_(); \ } while (0) -#endif #ifdef FLEXIMG_DEBUG #define FLEXIMG_DEBUG_WARN(fmt, ...) FLEXIMG_DEBUG_LOG(fmt, __VA_ARGS__) diff --git a/src/fleximg/core/memory/platform.h b/src/fleximg/core/memory/platform.h index a3cde43..908cf64 100644 --- a/src/fleximg/core/memory/platform.h +++ b/src/fleximg/core/memory/platform.h @@ -111,48 +111,4 @@ class DefaultPlatformMemory : public IPlatformMemory { } // namespace core } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "allocator.h" - -namespace FLEXIMG_NAMESPACE { -namespace core { -namespace memory { - -// グローバルプラットフォームメモリインスタンス -static IPlatformMemory *s_platformMemory = nullptr; - -IPlatformMemory &getPlatformMemory() -{ - if (!s_platformMemory) { - s_platformMemory = &DefaultPlatformMemory::instance(); - } - return *s_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::deallocate(void *ptr) -{ - DefaultAllocator::instance().deallocate(ptr); -} - -} // namespace memory -} // namespace core -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 d918f64..7bcd758 100644 --- a/src/fleximg/core/memory/pool_allocator.h +++ b/src/fleximg/core/memory/pool_allocator.h @@ -13,6 +13,7 @@ #include #include "../common.h" +#include "../perf_metrics.h" // FLEXIMG_DEBUG_PERF_METRICS マクロ #include "allocator.h" namespace FLEXIMG_NAMESPACE { @@ -259,167 +260,4 @@ class PoolAllocatorAdapter : public IAllocator { } // namespace core } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { -namespace core { -namespace memory { - -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; -} - -void *PoolAllocator::allocate(size_t size) -{ - if (!initialized_ || size == 0) { - return nullptr; - } - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.totalAllocations++; -#endif - - // 必要なブロック数を計算 - size_t blocksNeeded = (size + blockSize_ - 1) / blockSize_; - - if (blocksNeeded > blockCount_) { -#ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.misses++; -#endif - return nullptr; - } - - // 必要なビットパターンを作成 - uint32_t needBitmap = (1U << blocksNeeded) - 1; - - // 探索方向を決定(交互に切り替えてフラグメンテーション軽減) - size_t start = searchFromHead_ ? 0 : blockCount_ - blocksNeeded; - size_t end = blockCount_ - blocksNeeded + 1; - bool forward = searchFromHead_; - - searchFromHead_ = !searchFromHead_; // 次回は逆方向 - - // ビットマップで連続空きブロックを探索 - 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); // 確保ブロック数を記録 -#ifdef FLEXIMG_DEBUG_PERF_METRICS - 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_); - } - } - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.misses++; -#endif - return nullptr; -} - -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); - - if (p < poolStart || p >= poolEnd) { - return false; // プール外 - } - - // ブロックインデックス計算 - size_t blockIndex = static_cast(p - poolStart) / blockSize_; - - if (blockIndex >= blockCount_) { - return false; // 範囲外 - } - - // ビットが立っているか確認(確保済みか) - if ((allocatedBitmap_ & (1U << blockIndex)) == 0) { - return false; // 二重解放 - } - - // 確保ブロック数を取得 - uint8_t blocksToFree = blockCounts_[blockIndex]; - if (blocksToFree == 0) { - blocksToFree = 1; // フォールバック(通常は起きない) - } - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.totalDeallocations++; -#endif - - // 確保時のブロック数分のビットをクリア - uint32_t freeBitmap = ((1U << blocksToFree) - 1) << blockIndex; - allocatedBitmap_ &= ~freeBitmap; - blockCounts_[blockIndex] = 0; // 記録をクリア -#ifdef FLEXIMG_DEBUG_PERF_METRICS - stats_.allocatedBitmap = allocatedBitmap_; -#endif - - return true; -} - -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 - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_CORE_MEMORY_POOL_ALLOCATOR_H diff --git a/src/fleximg/core/node.h b/src/fleximg/core/node.h index d003697..3598823 100644 --- a/src/fleximg/core/node.h +++ b/src/fleximg/core/node.h @@ -593,130 +593,4 @@ using core::Node; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { -namespace core { - -// ============================================================================ -// Node - ヘルパーメソッド実装 -// ============================================================================ - -// 循環参照チェック(pullPrepare/pushPrepare共通) -// 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; // 成功(処理継続) -} - -// フォーマット変換ヘルパー(メトリクス記録付き) -// 参照モードから所有モードに変わった場合、ノード別統計に記録 -// 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()) { -#ifdef FLEXIMG_DEBUG_PERF_METRICS - PerfMetrics::instance().nodes[nodeTypeForMetrics()].recordAlloc(result.totalBytes(), result.width(), - result.height()); -#endif - } - 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::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を構築 -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 - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_NODE_H diff --git a/src/fleximg/fleximg.cpp b/src/fleximg/fleximg.cpp index f3ef393..02d0665 100644 --- a/src/fleximg/fleximg.cpp +++ b/src/fleximg/fleximg.cpp @@ -3,36 +3,28 @@ * @brief fleximg ライブラリ実装のコンパイル単位 * * このファイルは fleximg ライブラリの唯一のコンパイル単位です。 - * FLEXIMG_IMPLEMENTATION を定義してから各ヘッダをincludeすることで、 - * 実装部がここでのみコンパイルされます。 - * - * stb ライブラリ方式(Implementation Macro パターン)を採用しています。 + * 宣言ヘッダ(src/fleximg/)と実装ファイル(impl/fleximg/)を + * インクルードし、単一コンパイル単位として全体をビルドします。 */ -#define FLEXIMG_IMPLEMENTATION - // ============================================================================= -// Core +// 宣言ヘッダ // ============================================================================= + +// Core #include "core/affine_capability.h" #include "core/memory/platform.h" #include "core/memory/pool_allocator.h" #include "core/node.h" -// ============================================================================= // Image -// ============================================================================= #include "image/pixel_format.h" #include "image/viewport.h" -// ============================================================================= // Operations -// ============================================================================= #include "operations/filters.h" -// ============================================================================= // Nodes -// ============================================================================= #include "nodes/affine_node.h" #include "nodes/composite_node.h" #include "nodes/distributor_node.h" @@ -44,3 +36,32 @@ #include "nodes/sink_node.h" #include "nodes/source_node.h" #include "nodes/vertical_blur_node.h" + +// ============================================================================= +// 実装 (impl/) +// ============================================================================= + +// Core +#include "../../impl/fleximg/core/memory/platform.inl" +#include "../../impl/fleximg/core/memory/pool_allocator.inl" +#include "../../impl/fleximg/core/node.inl" + +// Image +#include "../../impl/fleximg/image/pixel_format.inl" +#include "../../impl/fleximg/image/viewport.inl" + +// Operations +#include "../../impl/fleximg/operations/filters.inl" + +// Nodes +#include "../../impl/fleximg/nodes/affine_node.inl" +#include "../../impl/fleximg/nodes/composite_node.inl" +#include "../../impl/fleximg/nodes/distributor_node.inl" +#include "../../impl/fleximg/nodes/filter_node_base.inl" +#include "../../impl/fleximg/nodes/horizontal_blur_node.inl" +#include "../../impl/fleximg/nodes/matte_node.inl" +#include "../../impl/fleximg/nodes/ninepatch_source_node.inl" +#include "../../impl/fleximg/nodes/renderer_node.inl" +#include "../../impl/fleximg/nodes/sink_node.inl" +#include "../../impl/fleximg/nodes/source_node.inl" +#include "../../impl/fleximg/nodes/vertical_blur_node.inl" diff --git a/src/fleximg/image/pixel_format.h b/src/fleximg/image/pixel_format.h index fa87f72..6ae9c2b 100644 --- a/src/fleximg/image/pixel_format.h +++ b/src/fleximg/image/pixel_format.h @@ -249,7 +249,7 @@ namespace pixel_format { namespace detail { // BytesPerPixel別 DDA転写関数(前方宣言) -// 実装は dda.h で提供(FLEXIMG_IMPLEMENTATION部) +// 実装は impl/fleximg/image/pixel_format/dda.inl で提供 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); @@ -262,7 +262,7 @@ void copyQuadDDA_3Byte(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, 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 インクルード後) +// 実装は impl/fleximg/image/pixel_format/dda.inl で提供 template void copyRowDDA_Bit(uint8_t *dst, const uint8_t *srcData, int_fast16_t count, const DDAParam *param); @@ -290,56 +290,6 @@ inline void lut8to16(uint16_t *d, const uint8_t *s, size_t pixelCount, const uin } // namespace FLEXIMG_NAMESPACE -// ------------------------------------------------------------------------ -// 内部ヘルパー関数(実装部) -// ------------------------------------------------------------------------ -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { -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); -} - -// 明示的インスタンス化(非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 *); - -} // namespace detail -} // namespace pixel_format -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - // ======================================================================== // 各ピクセルフォーマット(個別ヘッダ) // ======================================================================== @@ -352,11 +302,6 @@ template void lut8toN(uint32_t *, const uint8_t *, size_t, const uint3 #include "pixel_format/rgb888.h" #include "pixel_format/rgba8_straight.h" -// DDA関数(bit_packed_detail が定義された後にインクルード) -#ifdef FLEXIMG_IMPLEMENTATION -#include "pixel_format/dda.h" -#endif - namespace FLEXIMG_NAMESPACE { // ======================================================================== @@ -487,7 +432,4 @@ inline void convertFormat(const void *src, PixelFormatID srcFormat, void *dst, P } // namespace FLEXIMG_NAMESPACE -// FormatConverter 実装(FLEXIMG_IMPLEMENTATION ガード内) -#include "pixel_format/format_converter.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 d134861..18b7613 100644 --- a/src/fleximg/image/pixel_format/alpha8.h +++ b/src/fleximg/image/pixel_format/alpha8.h @@ -20,77 +20,4 @@ inline const PixelFormatID Alpha8 = &BuiltinFormats::Alpha8; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// Alpha8: 単一アルファチャンネル ↔ RGBA8_Straight 変換 -// ======================================================================== - -// 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 - } -} - -// 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チャンネル抽出 - } -} - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - true, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_PIXEL_FORMAT_ALPHA8_H diff --git a/src/fleximg/image/pixel_format/grayscale.h b/src/fleximg/image/pixel_format/grayscale.h index 725d956..5284652 100644 --- a/src/fleximg/image/pixel_format/grayscale.h +++ b/src/fleximg/image/pixel_format/grayscale.h @@ -36,410 +36,4 @@ inline const PixelFormatID Grayscale4_LSB = &BuiltinFormats::Grayscale4_LSB; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// Grayscale8: 単一輝度チャンネル ↔ RGBA8_Straight 変換 -// ======================================================================== - -// 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 - } -} - -// 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; - } -} - -// ------------------------------------------------------------------------ -// Grayscale8 フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -// ======================================================================== -// ビット操作ヘルパー関数(bit-packed Grayscale/Index共用) -// ======================================================================== - -namespace bit_packed_detail { - -// ======================================================================== -// unpackIndexBits: packed bytes → 8bit value array -// ======================================================================== - -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; - } - } - - ++byteIdx; - pixelIdx = 0; // 次のバイトからは先頭から読む - } -} - -// ======================================================================== -// packIndexBits: 8bit value array → packed bytes -// ======================================================================== - -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)); - } - } - dst[i] = b; - src += PixelsPerByte; - pixelCount -= PixelsPerByte; - } -} - -// ======================================================================== -// ビット単位アクセスヘルパー(LovyanGFXスタイル) -// ======================================================================== - -// 指定座標のピクセルを 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; - } -} - -} // namespace bit_packed_detail - -// ======================================================================== -// GrayscaleN: bit-packed Grayscale ↔ RGBA8_Straight 変換 -// ======================================================================== - -// GrayscaleN → RGBA8_Straight(bit-packed → RGBA8) -// 末尾詰め方式: -// 出力バッファ(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); -} - -// 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; - } - - // パック - bit_packed_detail::packIndexBits(dstPtr, grayBuf, chunk); - - srcPtr += chunk * 4; - dstPtr += (chunk + MaxPixelsPerByte - 1) / MaxPixelsPerByte; - remaining -= chunk; - } -} - -// ------------------------------------------------------------------------ -// Bit-packed Grayscale Formats フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -// Forward declarations for sibling references -extern const PixelFormatDescriptor Grayscale1_LSB; -extern const PixelFormatDescriptor Grayscale2_LSB; -extern const PixelFormatDescriptor Grayscale4_LSB; - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 1, // bitsPerPixel - 1, // bytesPerPixel - 8, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - false, // isIndexed -}; - -const PixelFormatDescriptor Grayscale1_LSB = { - "Grayscale1_LSB", - grayscaleN_toStraight<1, BitOrder::LSBFirst>, - grayscaleN_fromStraight<1, BitOrder::LSBFirst>, - nullptr, - nullptr, - &Grayscale1_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<1, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 0, - 1, - 1, - 8, - 1, - 1, - false, - false, -}; - -const PixelFormatDescriptor Grayscale2_MSB = { - "Grayscale2_MSB", - grayscaleN_toStraight<2, BitOrder::MSBFirst>, - grayscaleN_fromStraight<2, BitOrder::MSBFirst>, - nullptr, - nullptr, - &Grayscale2_LSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<2, BitOrder::MSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::MSBFirst>, - BitOrder::MSBFirst, - ByteOrder::Native, - 0, - 2, - 1, - 4, - 1, - 1, - false, - false, -}; - -const PixelFormatDescriptor Grayscale2_LSB = { - "Grayscale2_LSB", - grayscaleN_toStraight<2, BitOrder::LSBFirst>, - grayscaleN_fromStraight<2, BitOrder::LSBFirst>, - nullptr, - nullptr, - &Grayscale2_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<2, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 0, - 2, - 1, - 4, - 1, - 1, - false, - false, -}; - -const PixelFormatDescriptor Grayscale4_MSB = { - "Grayscale4_MSB", - grayscaleN_toStraight<4, BitOrder::MSBFirst>, - grayscaleN_fromStraight<4, BitOrder::MSBFirst>, - nullptr, - nullptr, - &Grayscale4_LSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<4, BitOrder::MSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::MSBFirst>, - BitOrder::MSBFirst, - ByteOrder::Native, - 0, - 4, - 1, - 2, - 1, - 1, - false, - false, -}; - -const PixelFormatDescriptor Grayscale4_LSB = { - "Grayscale4_LSB", - grayscaleN_toStraight<4, BitOrder::LSBFirst>, - grayscaleN_fromStraight<4, BitOrder::LSBFirst>, - nullptr, - nullptr, - &Grayscale4_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<4, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 0, - 4, - 1, - 2, - 1, - 1, - false, - false, -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 b5dab3e..99ca34e 100644 --- a/src/fleximg/image/pixel_format/index.h +++ b/src/fleximg/image/pixel_format/index.h @@ -43,293 +43,4 @@ inline const PixelFormatID Index8 = &BuiltinFormats::Index8; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// 共通パレットLUT関数(__restrict__ なし、in-place安全) -// ======================================================================== -// -// インデックス値(uint8_t配列)をパレットフォーマットのピクセルに展開する。 -// index8_expandIndex / indexN_expandIndex 双方から呼ばれる共通実装。 -// __restrict__ なしのため、末尾詰め方式のin-place展開にも対応。 -// -// 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)); - } - } -} - -// ======================================================================== -// 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); -} - -// ======================================================================== -// Index8: RGBA8_Straight → Index8 変換 -// ======================================================================== -// -// RGBカラーからインデックス値への変換。 -// パレットなし時はBT.601輝度計算にフォールバック(grayscale8_fromStraightに委譲)。 -// 将来的にパレットへの最近傍色マッチングに拡張予定。 -// - -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); -} - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -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で実施) - BitOrder::MSBFirst, - ByteOrder::Native, - 256, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - true, // isIndexed -}; - -// ======================================================================== -// Bit-packed Index Formats (Index1/2/4 MSB/LSB) -// ======================================================================== - -// 変換関数: expandIndex (パレット展開) -// 末尾詰め方式: 出力バッファ末尾に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); -} - -// 変換関数: 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); -} - -// ------------------------------------------------------------------------ -// Bit-packed Index Formats フォーマット定義 -// ------------------------------------------------------------------------ - -// Forward declarations for sibling references -extern const PixelFormatDescriptor Index1_LSB; -extern const PixelFormatDescriptor Index2_LSB; -extern const PixelFormatDescriptor Index4_LSB; - -const PixelFormatDescriptor Index1_MSB = { - "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 - BitOrder::MSBFirst, - ByteOrder::Native, - 2, // maxPaletteSize - 1, // bitsPerPixel - 1, // bytesPerPixel - 8, // pixelsPerUnit - 1, // bytesPerUnit - 1, // channelCount - false, // hasAlpha - true, // isIndexed -}; - -const PixelFormatDescriptor Index1_LSB = { - "Index1_LSB", - grayscaleN_toStraight<1, BitOrder::LSBFirst>, - indexN_fromStraight<1, BitOrder::LSBFirst>, - indexN_expandIndex<1, BitOrder::LSBFirst>, - nullptr, - &Index1_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<1, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<1, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 2, // maxPaletteSize - 1, - 1, - 8, - 1, - 1, - false, - true, -}; - -const PixelFormatDescriptor Index2_MSB = { - "Index2_MSB", - grayscaleN_toStraight<2, BitOrder::MSBFirst>, - indexN_fromStraight<2, BitOrder::MSBFirst>, - indexN_expandIndex<2, BitOrder::MSBFirst>, - nullptr, - &Index2_LSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<2, BitOrder::MSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::MSBFirst>, - BitOrder::MSBFirst, - ByteOrder::Native, - 4, // maxPaletteSize - 2, - 1, - 4, - 1, - 1, - false, - true, -}; - -const PixelFormatDescriptor Index2_LSB = { - "Index2_LSB", - grayscaleN_toStraight<2, BitOrder::LSBFirst>, - indexN_fromStraight<2, BitOrder::LSBFirst>, - indexN_expandIndex<2, BitOrder::LSBFirst>, - nullptr, - &Index2_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<2, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<2, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 4, // maxPaletteSize - 2, - 1, - 4, - 1, - 1, - false, - true, -}; - -const PixelFormatDescriptor Index4_MSB = { - "Index4_MSB", - grayscaleN_toStraight<4, BitOrder::MSBFirst>, - indexN_fromStraight<4, BitOrder::MSBFirst>, - indexN_expandIndex<4, BitOrder::MSBFirst>, - nullptr, - &Index4_LSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<4, BitOrder::MSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::MSBFirst>, - BitOrder::MSBFirst, - ByteOrder::Native, - 16, // maxPaletteSize - 4, - 1, - 2, - 1, - 1, - false, - true, -}; - -const PixelFormatDescriptor Index4_LSB = { - "Index4_LSB", - grayscaleN_toStraight<4, BitOrder::LSBFirst>, - indexN_fromStraight<4, BitOrder::LSBFirst>, - indexN_expandIndex<4, BitOrder::LSBFirst>, - nullptr, - &Index4_MSB, - nullptr, - pixel_format::detail::copyRowDDA_Bit<4, BitOrder::LSBFirst>, - pixel_format::detail::copyQuadDDA_Bit<4, BitOrder::LSBFirst>, - BitOrder::LSBFirst, - ByteOrder::Native, - 16, // maxPaletteSize - 4, - 1, - 2, - 1, - 1, - false, - true, -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 01a10bc..258a2a9 100644 --- a/src/fleximg/image/pixel_format/rgb332.h +++ b/src/fleximg/image/pixel_format/rgb332.h @@ -20,118 +20,4 @@ inline const PixelFormatID RGB332 = &BuiltinFormats::RGB332; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// RGB332: 8bit RGB (3-3-2) -// ======================================================================== - -// RGB332 → RGBA8 変換ルックアップテーブル -// RGB332の256通りの値に対して、RGBA8値を事前計算 -// 各エントリ: uint32_t (リトルエンディアン: R8 | G8<<8 | B8<<16 | A8<<24) -// 32bitロード/ストアで効率的に変換可能 -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) - -// 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(0xc0), RGB332_ROW(0xd0), RGB332_ROW(0xe0), RGB332_ROW(0xf0)}; - -#undef RGB332_ENTRY -#undef RGB332_ROW - -} // 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); -} - -// 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; - } -} -#undef RGBA8_TO_RGB332 - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 8, // bitsPerPixel - 1, // bytesPerPixel - 1, // pixelsPerUnit - 1, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 af77ace..c97e1a8 100644 --- a/src/fleximg/image/pixel_format/rgb565.h +++ b/src/fleximg/image/pixel_format/rgb565.h @@ -22,330 +22,4 @@ inline const PixelFormatID RGB565_BE = &BuiltinFormats::RGB565_BE; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// RGB565_LE: 16bit RGB (Little Endian) -// ======================================================================== - -// RGB565 → RGB8 変換ルックアップテーブル -// RGB565の16bit値を上位バイトと下位バイトに分けて処理 -// -// RGB565構造 (16bit): RRRRR GGGGGG BBBBB -// high_byte: RRRRRGGG (R5全部 + G6上位3bit) -// low_byte: GGGBBBBB (G6下位3bit + B5全部) -// -// G8の分離計算: -// G8 = (G6 << 2) | (G6 >> 4) -// = (high_G3 << 5) + (high_G3 >> 1) + (low_G3 << 2) + (low_G3 >> 4) -// ※ low_G3 >> 4 は low_G3 が 0-7 なので常に 0 -// -// テーブル構成 (uint16_t配列、リトルエンディアン前提): -// high_table[high_byte] = (G_high << 8) | R8 where G_high = (high_G3 << 5) + -// (high_G3 >> 1) low_table[low_byte] = (G_low << 8) | B8 where G_low = -// low_G3 << 2 -// -// 両テーブルとも上位バイトに緑成分を配置 -// -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))) - -// 下位バイト用エントリ: 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) - -// 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下位バイト用テーブル (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)}; - -#undef RGB565_HIGH_ENTRY -#undef RGB565_LOW_ENTRY -#undef RGB565_HIGH_ROW -#undef RGB565_LOW_ROW - -} // 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; - } -} -#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; - } -} - -// ======================================================================== -// RGB565_BE: 16bit RGB (Big Endian) -// ======================================================================== - -// 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; - } -} -#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; - } -} - -// ======================================================================== -// 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)); - } -} - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -// Forward declaration for sibling reference -extern const PixelFormatDescriptor RGB565_BE; - -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 - BitOrder::MSBFirst, - ByteOrder::LittleEndian, - 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 - BitOrder::MSBFirst, - ByteOrder::BigEndian, - 0, // maxPaletteSize - 16, // bitsPerPixel - 2, // bytesPerPixel - 1, // pixelsPerUnit - 2, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 298d17f..e857c9e 100644 --- a/src/fleximg/image/pixel_format/rgb888.h +++ b/src/fleximg/image/pixel_format/rgb888.h @@ -22,251 +22,4 @@ inline const PixelFormatID BGR888 = &BuiltinFormats::BGR888; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -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_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_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; - } -} - -// ======================================================================== -// エンディアン・バイトスワップ関数 -// ======================================================================== - -// 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]; - } -} - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -// Forward declarations for sibling references -extern const PixelFormatDescriptor BGR888; - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 24, // bitsPerPixel - 3, // bytesPerPixel - 1, // pixelsPerUnit - 3, // bytesPerUnit - 3, // channelCount - false, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #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 56cebd0..66b2ed3 100644 --- a/src/fleximg/image/pixel_format/rgba8_straight.h +++ b/src/fleximg/image/pixel_format/rgba8_straight.h @@ -20,272 +20,4 @@ inline const PixelFormatID RGBA8_Straight = &BuiltinFormats::RGBA8_Straight; } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../../core/format_metrics.h" - -namespace FLEXIMG_NAMESPACE { - -// ======================================================================== -// RGBA8_Straight 変換関数 -// 標準フォーマット: RGBA8_Straight(8bit RGBA、ストレートアルファ) -// ======================================================================== - -// 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_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合成(背面への合成) -// -// under合成の数式: -// resultA = dstA + srcA * (1 - dstA/255) -// resultColor = (dstColor * dstA + srcColor * srcA * (1 - dstA/255)) / -// resultA -// -// 処理パターン: -// - dstA == 255(不透明): スキップ(背面は見えない) -// - dstA == 0(透明): srcをコピー -// - srcA == 0(透明): スキップ(合成対象なし) -// - それ以外: ブレンド計算 -// -// 最適化手法: -// 1. gotoラベル方式のディスパッチ(分岐予測しやすい) -// 2. 4ピクセル単位の連続領域高速スキップ/コピー -// 3. 正規化重み方式によるブレンド計算の効率化: -// - 重みを合計256に正規化し、シフトで除算を代替 -// - 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; -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(正確な計算) - - 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; - - // 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 = static_cast(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; -} - - // ======================================================================== - // 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 = static_cast(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; -} - - // ======================================================================== - // 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 = static_cast(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; -} -} - -// ------------------------------------------------------------------------ -// フォーマット定義 -// ------------------------------------------------------------------------ - -namespace BuiltinFormats { - -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 - BitOrder::MSBFirst, - ByteOrder::Native, - 0, // maxPaletteSize - 32, // bitsPerPixel - 4, // bytesPerPixel - 1, // pixelsPerUnit - 4, // bytesPerUnit - 4, // channelCount - true, // hasAlpha - false, // isIndexed -}; - -} // namespace BuiltinFormats - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_PIXEL_FORMAT_RGBA8_STRAIGHT_H diff --git a/src/fleximg/image/viewport.h b/src/fleximg/image/viewport.h index ab5762e..e7994c0 100644 --- a/src/fleximg/image/viewport.h +++ b/src/fleximg/image/viewport.h @@ -152,403 +152,4 @@ inline bool canUseSingleChannelBilinear(PixelFormatID formatID, uint8_t edgeFade } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include "../operations/transform.h" -#include -#include - -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 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 { - -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); - } -} - -// ============================================================================ -// バイリニア補間関数(RGBA8888固定) -// ============================================================================ -// -// copyQuadDDAで抽出した4ピクセルデータからバイリニア補間を実行する。 -// 入力: quadPixels = [p00,p10,p01,p11] × count(各ピクセル4bytes、RGBA8888) -// 境界外ピクセルは呼び出し前にゼロ埋めされていること -// 出力: 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; - } -} - -// ============================================================================ -// バイリニア補間関数(1チャンネル固定: Alpha8/Grayscale8用) -// ============================================================================ -// -// copyQuadDDAで抽出した4ピクセルデータからバイリニア補間を実行する。 -// 入力: quadPixels = [p00,p10,p01,p11] × count(各ピクセル1byte) -// 境界外ピクセルは呼び出し前にゼロ埋めされていること -// 出力: 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); - } -} - -// ============================================================================ -// copyRowDDABilinear -// ============================================================================ -// -// 処理フロー(チャンクループ): -// a. copyQuadDDA: 4ピクセル抽出 + edgeFlags生成 -// b. convertFormat: フォーマット変換(RGBA8_Straight以外の場合) -// c. edgeFlags適用: 境界ピクセルのアルファを0化 -// 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; - - // 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パス: 通常のマルチチャンネルフォーマット - // ======================================================================== - - // チャンク処理用定数 - 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, static_cast(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; - } - } - - // バイリニア補間 - bilinearBlend_RGBA8888(dstPtr, quadRGBA, weightsXY, chunk); - - // 次のチャンクへ - dstPtr += chunk; - param.srcX += incrX * chunk; - param.srcY += incrY * 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; - - 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); - } -} - -} // namespace view_ops -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_VIEWPORT_H diff --git a/src/fleximg/nodes/affine_node.h b/src/fleximg/nodes/affine_node.h index 5aa9dd9..a65abab 100644 --- a/src/fleximg/nodes/affine_node.h +++ b/src/fleximg/nodes/affine_node.h @@ -71,88 +71,4 @@ class AffineNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { - -// ============================================================================ -// AffineNode - Template Method フック実装 -// ============================================================================ - -// 複数の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; -} - -// 複数の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; -} - -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); - } -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_AFFINE_NODE_H diff --git a/src/fleximg/nodes/composite_node.h b/src/fleximg/nodes/composite_node.h index 397ea8d..2745792 100644 --- a/src/fleximg/nodes/composite_node.h +++ b/src/fleximg/nodes/composite_node.h @@ -107,210 +107,4 @@ class CompositeNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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; - } - } - - // 全上流へ伝播し、結果をマージ(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; - } - } 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; -} - -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; -} - -// onPullProcess: 複数の上流から画像を取得してunder合成 -// 単一バッファ事前確保方式: -// - 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; - } - - FLEXIMG_METRICS_SCOPE(NodeType::Composite); - - // 上流のバッファをblendFrom - if (input.hasBuffer()) { - compositeBuf->blendFrom(input.buffer()); - } - - context_->releaseResponse(input); - } - - resp.origin = compositeOrigin; - return resp; -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_COMPOSITE_NODE_H diff --git a/src/fleximg/nodes/distributor_node.h b/src/fleximg/nodes/distributor_node.h index bee7a30..090dedd 100644 --- a/src/fleximg/nodes/distributor_node.h +++ b/src/fleximg/nodes/distributor_node.h @@ -93,167 +93,4 @@ class DistributorNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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; - } - } - - // 全下流へ伝播し、結果をマージ(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; - } - } else { - // 下流がない場合はサイズ0を返す - // width/height/originはデフォルト値(0)のまま - } - - return merged; -} - -void DistributorNode::onPushFinalize() -{ - // 全下流へ伝播 - int numOutputs = outputCount(); - for (int i = 0; i < numOutputs; ++i) { - Node *downstream = downstreamNode(i); - if (downstream) { - downstream->pushFinalize(); - } - } - finalize(); -} - -void DistributorNode::onPushProcess(RenderResponse &input, const RenderRequest &request) -{ - // プッシュ型単一入力: 無効なら処理終了 - if (!input.isValid()) { - return; - } - - // バッファ準備 - consolidateIfNeeded(input); - - FLEXIMG_METRICS_SCOPE(NodeType::Distributor); - - int numOutputs = outputCount(); - int validOutputs = 0; - - // 接続されている出力を数える - for (int i = 0; i < numOutputs; ++i) { - if (downstreamNode(i)) { - ++validOutputs; - } - } - - 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); - } - } -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_DISTRIBUTOR_NODE_H diff --git a/src/fleximg/nodes/filter_node_base.h b/src/fleximg/nodes/filter_node_base.h index 912ef29..51d7c6a 100644 --- a/src/fleximg/nodes/filter_node_base.h +++ b/src/fleximg/nodes/filter_node_base.h @@ -89,72 +89,4 @@ class FilterNodeBase : public Node { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { - -// ============================================================================ -// FilterNodeBase - Template Method フック実装 -// ============================================================================ - -RenderResponse &FilterNodeBase::onPullProcess(const RenderRequest &request) -{ - Node *upstream = upstreamNode(0); - if (!upstream) return makeEmptyResponse(request.origin); - - 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); -#endif - - RenderResponse &input = upstream->pullProcess(inputReq); - if (!input.isValid()) return input; - - // process() を呼ぶ(Node基底クラスの設計に沿う) - return process(input, request); -} - -// ============================================================================ -// FilterNodeBase - process() 共通実装 -// ============================================================================ -// -// スキャンライン必須仕様(height=1)前提の共通処理: -// 1. RGBA8_Straight形式に変換 -// 2. ラインフィルタ関数を適用 -// 3. パフォーマンス計測(デバッグビルド時) -// - -RenderResponse &FilterNodeBase::process(RenderResponse &input, const RenderRequest &request) -{ - (void)request; // スキャンライン必須仕様では未使用 - FLEXIMG_METRICS_SCOPE(nodeTypeForMetrics()); - - // フォーマット変換を実行(メトリクス記録付き) - consolidateIfNeeded(input, PixelFormatIDs::RGBA8_Straight); - - // 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_); - - // inputをそのまま返す(借用元への変更が反映される) - return input; -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_FILTER_NODE_BASE_H diff --git a/src/fleximg/nodes/horizontal_blur_node.h b/src/fleximg/nodes/horizontal_blur_node.h index 496acc0..f31b137 100644 --- a/src/fleximg/nodes/horizontal_blur_node.h +++ b/src/fleximg/nodes/horizontal_blur_node.h @@ -177,276 +177,4 @@ class HorizontalBlurNode : public Node { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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; - } - - // 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; -} - -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; -#endif - - // RGBA8_Straightに変換 - ImageBuffer buffer = convertFormat(ImageBuffer(input.buffer()), PixelFormatIDs::RGBA8_Straight); - - // 上流から返された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); - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - 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}); -} - -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); - } - - // 下流に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); - } -} - -// ============================================================================ -// HorizontalBlurNode - private ヘルパーメソッド実装 -// ============================================================================ - -// 水平方向ブラー処理(共通) -// 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; - } - } - 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); - } -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_HORIZONTAL_BLUR_NODE_H diff --git a/src/fleximg/nodes/matte_node.h b/src/fleximg/nodes/matte_node.h index ff06bb0..d8b3714 100644 --- a/src/fleximg/nodes/matte_node.h +++ b/src/fleximg/nodes/matte_node.h @@ -167,758 +167,4 @@ class MatteNode : public Node { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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; - } - } - } - - 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; -} - -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; - } - } - - rangeCache_.unionRange = (startX < endX) ? DataRange{startX, endX} : DataRange{}; - rangeCache_.origin = request.origin; - rangeCache_.valid = true; - - return rangeCache_.unionRange; -} - -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); -} - -// ============================================================================ -// MatteNode - onPullProcess実装(最適化版) -// ============================================================================ -// -// 処理フロー: -// 1. mask有効範囲の確定(早期リターン) -// - maskデータなし / 取得失敗 / 全面0 → bg直接返却(変換なし) -// 2. bg取得・出力領域計算 -// 3. bg戦略決定・出力バッファ作成 -// 4. fg取得(mask有効範囲のみ) -// 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; - } - - // fg∪bgが空 → マスク値に関わらず出力は透明 - if (fgBgStart >= fgBgEnd) { - rangeCache_.valid = false; - return makeEmptyResponse(request.origin); - } - - // 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; - } - } - - RenderResponse &maskResult = maskNode->pullProcess(maskRequest); - if (!maskResult.isValid()) goto fallback_bg; - - // バッファ準備 - consolidateIfNeeded(maskResult); - - // Alpha8に変換 - if (maskResult.buffer().formatID() != PixelFormatIDs::Alpha8) { - maskResult.convertFormat(PixelFormatIDs::Alpha8); - } - - // 全面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されたビューを再取得 - } - - // ======================================================================== - // Step 2: bg取得・出力領域計算 - // ======================================================================== - - RenderResponse *bgResultPtr = nullptr; - if (rangeCache_.bgRange.hasData() && bgNode) { - RenderResponse &bgResult = bgNode->pullProcess(request); - if (bgResult.isValid()) { - // バッファ準備 - consolidateIfNeeded(bgResult); - bgResultPtr = &bgResult; - } - } - - // 出力領域計算(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; - } - - auto unionWidth = static_cast(from_fixed(unionMaxX - unionMinX)); - auto unionHeight = static_cast(from_fixed(unionMaxY - unionMinY)); - - // ======================================================================== - // Step 3: 出力バッファ作成(ゼロクリア)+ bgコピー - // ======================================================================== - - FLEXIMG_METRICS_SCOPE(NodeType::Matte); - - 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()); -#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, static_cast(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); - } - fgResultPtr = &fgResult; - } - } - - // ======================================================================== - // Step 5: 合成 - // ======================================================================== - - // 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); - - applyMatteOverlay(outputBuf, unionWidth, fgView, maskInputView); - - // キャッシュ無効化 - rangeCache_.valid = false; - - return makeResponse(std::move(outputBuf), Point{unionMinX, unionMinY}); - } - - // bgフォールバック: mask無効時はbgを直接返却 -fallback_bg: - 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バイト単位(ポインタベース) - { - 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; - } - } - - // 全面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; - } - - // 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; - } - } - - outRightSkip = rightSkip; - return maskWidth - leftSkip - rightSkip; -} - -// ============================================================================ -// MatteNode - 合成処理実装 -// ============================================================================ - -// ---------------------------------------------------------------------------- -// processRowNoFg: fgなし領域の行処理 -// - alpha=0: スキップ(出力には既にbgがある) -// - 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; - - uint_fast8_t alpha = *m; - - if (alpha == 0) goto handle_alpha_0; - if (alpha == 255) goto handle_alpha_255; - -blend: - // ブレンドループ: 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; - // 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; - -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 (alpha == 0) goto handle_alpha_0; - if (alpha == 255) goto handle_alpha_255; - goto blend; -} - -// ---------------------------------------------------------------------------- -// processRowWithFg: fg領域の行処理 -// - alpha=0: スキップ(出力には既にbgがある) -// - 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; - - uint_fast8_t alpha = *m; - - 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 { - ++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; - } - } - } - 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 (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)); - } - - // 中央領域: 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)); - } - } -} - -#if defined(BENCH_M5STACK) || defined(BENCH_NATIVE) -// ============================================================================ -// 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::benchProcessRowNoFg(uint8_t *d, const uint8_t *m, int pixelCount) -{ - processRowNoFg(d, m, static_cast(pixelCount)); -} -#endif - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_MATTE_NODE_H diff --git a/src/fleximg/nodes/ninepatch_source_node.h b/src/fleximg/nodes/ninepatch_source_node.h index 7ef38c8..813df5e 100644 --- a/src/fleximg/nodes/ninepatch_source_node.h +++ b/src/fleximg/nodes/ninepatch_source_node.h @@ -295,365 +295,4 @@ class NinePatchSourceNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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_; - } 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; -} - -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}); -} - -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::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; -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_NINEPATCH_SOURCE_NODE_H diff --git a/src/fleximg/nodes/renderer_node.h b/src/fleximg/nodes/renderer_node.h index 46d29be..b94b5dc 100644 --- a/src/fleximg/nodes/renderer_node.h +++ b/src/fleximg/nodes/renderer_node.h @@ -299,234 +299,4 @@ class RendererNode : public Node { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { - -// ============================================================================ -// RendererNode - 実行API実装 -// ============================================================================ - -PrepareStatus RendererNode::execPrepare() -{ -#ifdef FLEXIMG_DEBUG_PERF_METRICS - // メトリクスをリセット - 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; -} - -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可視化処理 -// - getDataRange()の範囲外: マゼンタ(データがないはずの領域) -// - 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]; - } - } - } - - // バッファ境界マーカーを追加(半透明オレンジ) - 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); - } - } - } - - // 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; -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_RENDERER_NODE_H diff --git a/src/fleximg/nodes/sink_node.h b/src/fleximg/nodes/sink_node.h index 95eeb2c..e6dad01 100644 --- a/src/fleximg/nodes/sink_node.h +++ b/src/fleximg/nodes/sink_node.h @@ -144,195 +144,4 @@ class SinkNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -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; - } 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 { - 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; -} - -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::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 - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_SINK_NODE_H diff --git a/src/fleximg/nodes/source_node.h b/src/fleximg/nodes/source_node.h index 7061431..929b7ae 100644 --- a/src/fleximg/nodes/source_node.h +++ b/src/fleximg/nodes/source_node.h @@ -208,438 +208,4 @@ class SourceNode : public Node, public AffineCapability { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { - -// ============================================================================ -// SourceNode - Template Method フック実装 -// ============================================================================ - -PrepareResponse SourceNode::onPullPrepare(const PrepareRequest &request) -{ - // 下流からの希望フォーマットを保存(将来のフォーマット最適化用) - preferredFormat_ = request.preferredFormat; - - // getDataRangeキャッシュを無効化(アフィン行列が変わる可能性があるため) - dataRangeCache_.invalidate(); - - // 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_; // 無変換時は単位行列 - } - - // 逆行列とピクセル中心オフセットを計算 - 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; - } - - // 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; -} - -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); -} - -// ============================================================================ -// SourceNode - private ヘルパーメソッド実装 -// ============================================================================ - -// スキャンライン有効範囲を計算(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; -} - -// 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; -} - -// アフィン変換付きプル処理(スキャンライン専用) -// 前提: 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; - } else { - outFormat = source_.formatID; - } - } - ImageBuffer *output = resp.createBuffer(validWidth, 1, outFormat, InitPolicy::Uninitialized); - - if (!output) { - return resp; // バッファ作成失敗時は空のResponseを返す - } - - // バッファにワールド座標originを設定(makeResponseを使わないパス) - output->setOrigin(adjustedOrigin); - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - 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; - - 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); - } - } - - // パレット情報を出力ImageBufferに設定 - if (palette_) { - output->setPalette(palette_); - } - // カラーキー情報を出力ImageBufferに設定 - if (colorKeyRGBA8_ != colorKeyReplace_) { - output->auxInfo().colorKeyRGBA8 = colorKeyRGBA8_; - output->auxInfo().colorKeyReplace = colorKeyReplace_; - } - - return resp; -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_SOURCE_NODE_H diff --git a/src/fleximg/nodes/vertical_blur_node.h b/src/fleximg/nodes/vertical_blur_node.h index 60583fa..a937a54 100644 --- a/src/fleximg/nodes/vertical_blur_node.h +++ b/src/fleximg/nodes/vertical_blur_node.h @@ -208,735 +208,4 @@ class VerticalBlurNode : public Node { } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -namespace FLEXIMG_NAMESPACE { - -// ======================================== -// 準備・終了処理 -// ======================================== - -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; - - // パイプライン方式でキャッシュを初期化(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_); -#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; -} - -// ======================================== -// 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}; -} - -// ======================================== -// 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; - } - - // スクリーン情報を保存(prepare()代わり) - screenWidth_ = request.width; - screenHeight_ = request.height; - screenOrigin_ = request.origin; - - // 上流の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); - -#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_); -#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); - - 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; -} - -void VerticalBlurNode::onPushProcess(RenderResponse &input, const RenderRequest &request) -{ - // radius=0の場合はスルー - if (radius_ == 0) { - Node *downstream = downstreamNode(0); - if (downstream) { - downstream->pushProcess(input, request); - } - 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); - - // 古い行を列合計から減算 - if (stage0.pushInputY >= ks) { - 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); - } - stage0.rowOriginX[static_cast(slot0)] = inputOrigin.x; - - // 新しい行を列合計に加算 - updateStageColSum(stage0, slot0, true); - - lastInputOriginY_ = inputOrigin.y; - stage0.pushInputY++; - - // 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(); - - // 残りの行を出力(下端はゼロパディング扱い) - 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); - - // radius=0の場合は処理をスキップしてスルー出力 - if (radius_ == 0) { - return upstream->pullProcess(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); - -#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; -#endif - - ImageBuffer output(outputWidth, 1, PixelFormatIDs::RGBA8_Straight, InitPolicy::Uninitialized); - -#ifdef FLEXIMG_DEBUG_PERF_METRICS - 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; - } - } - - // 出力の origin を計算(バッファ左上のワールド座標) - Point outputOrigin; - outputOrigin.x = interLeft; - outputOrigin.y = request.origin.y; - - 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(); - - // このステージへの最初の呼び出し時、currentYを調整してキャッシュを完全に充填 - // newY - kernelSize() - // から開始することで、kernelSize()回のループでキャッシュが充填される - if (!stage.cacheReady) { - stage.currentY = newY - ks; - stage.cacheReady = true; - } - - if (stage.currentY == newY) return; - - 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; - - // 古い行を列合計から減算 - updateStageColSum(stage, slot, false); - - // 新しい行を取得してキャッシュに格納 - 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; - } -} - -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 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}; -} - -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::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); - } -} - -// ======================================== -// 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); - } - - // 新しい行をキャッシュに格納 - 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; - } - } - - // 最終ステージが出力可能になったら下流に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; - } - } - - 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); - - RenderRequest outReq; - outReq.width = static_cast(cacheWidth_); - outReq.height = 1; - outReq.origin.x = originX; - outReq.origin.y = originY; - - pushOutputY_++; - - 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); - } -} - -} // namespace FLEXIMG_NAMESPACE - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_VERTICAL_BLUR_NODE_H diff --git a/src/fleximg/operations/filters.h b/src/fleximg/operations/filters.h index 1a8871a..ac3f9d9 100644 --- a/src/fleximg/operations/filters.h +++ b/src/fleximg/operations/filters.h @@ -47,70 +47,4 @@ void alpha_line(uint8_t *pixels, int_fast16_t count, const LineFilterParams &par } // namespace filters } // namespace FLEXIMG_NAMESPACE -// ============================================================================= -// 実装部 -// ============================================================================= -#ifdef FLEXIMG_IMPLEMENTATION - -#include -#include - -namespace FLEXIMG_NAMESPACE { -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))); - } - // 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); - - 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 - -#endif // FLEXIMG_IMPLEMENTATION - #endif // FLEXIMG_OPERATIONS_FILTERS_H