qmlfrontend/renderer/tiled_image_item.cpp
author unC0Rr
Mon, 17 Feb 2025 16:37:59 +0100
branchqmlrenderer
changeset 16089 02304ad06381
permissions -rw-r--r--
Add TiledImageItem and a scene that represents hedgewars map

#include "tiled_image_item.h"

#include <QSGImageNode>
#include <QSGTexture>

TiledImageItem::TiledImageItem(QQuickItem *parent) : QQuickItem{parent} {
  setFlag(ItemHasContents, true);
  setFlag(ItemObservesViewport, true);
}

TiledImageItem::~TiledImageItem() {}

void TiledImageItem::setImageData(int width, int height, uchar *pixels,
                                  QImage::Format format) {
  m_texture = QImage{pixels, width, height, width * 4, format};

  setImplicitWidth(width / window()->effectiveDevicePixelRatio());
  setImplicitHeight(height / window()->effectiveDevicePixelRatio());

  buildTiles();

  update();
}

void TiledImageItem::buildTiles() {
  m_tiles.clear();
  if (m_texture.isNull()) {
    return;
  }

  const auto imageWidth = m_texture.width();
  const auto imageHeight = m_texture.height();

  for (int y = 0; y < imageHeight; y += m_tileSize) {
    for (int x = 0; x < imageWidth; x += m_tileSize) {
      int w = qMin(m_tileSize, imageWidth - x);
      int h = qMin(m_tileSize, imageHeight - y);
      QImage tileImage{&m_texture.scanLine(y)[x * m_texture.depth() / 8],
                       m_tileSize, m_tileSize, m_texture.bytesPerLine(),
                       m_texture.format()};

      QRectF tileRect = QRect{x, y, w, h}.toRectF();
      tileRect.setTopLeft(tileRect.topLeft() /
                          window()->effectiveDevicePixelRatio());
      tileRect.setBottomRight(tileRect.bottomRight() /
                              window()->effectiveDevicePixelRatio());

      Tile tile;
      tile.outRect = tileRect;
      tile.image = tileImage;
      tile.dirty = true;
      m_tiles.emplace_back(std::move(tile));
    }
  }
}
int TiledImageItem::tileSize() const { return m_tileSize; }

void TiledImageItem::setTileSize(int newTileSize) {
  if (m_tileSize == newTileSize) {
    return;
  }

  m_tileSize = newTileSize;

  m_tiles.clear();
  m_dirty = true;

  Q_EMIT tileSizeChanged();
}

void TiledImageItem::test(const QString &fileName) {
  auto image = new QImage(fileName);

  setImageData(image->width(), image->height(), image->bits(), image->format());
}

QSGNode *TiledImageItem::updatePaintNode(QSGNode *oldNode,
                                         UpdatePaintNodeData *) {
  auto rootNode = oldNode;
  if (!rootNode) {
    rootNode = new QSGNode{};
  }

  if (!window()) {
    return rootNode;
  }

  const auto rect = clipRect();

  for (auto &tile : m_tiles) {
    if (!rect.intersects(tile.outRect)) {
      if (tile.node) {
        rootNode->removeChildNode(tile.node.get());
        tile.node.reset();
      }

      continue;
    }

    if (tile.dirty) {
      tile.texture.reset(window()->createTextureFromImage(tile.image));
      if (tile.node) {
        tile.node->setTexture(tile.texture.get());
      }

      tile.dirty = false;
    }

    if (!tile.node) {
      tile.node.reset(window()->createImageNode());
      tile.node->setRect(tile.outRect);
      tile.node->setTexture(tile.texture.get());
      rootNode->appendChildNode(tile.node.get());
    }
  }

  return rootNode;
}

TiledImageItem::Tile::Tile(Tile &&other) noexcept
    : outRect{other.outRect},
      image{std::move(other.image)},
      dirty{other.dirty},
      texture{std::move(other.texture)} {}