File indexing completed on 2024-04-28 04:32:47

0001 /*
0002     SPDX-FileCopyrightText: 2012 Mailson Menezes <mailson@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "tilesmanager_p.h"
0007 
0008 #include <QList>
0009 #include <QPainter>
0010 #include <QPixmap>
0011 #include <qmath.h>
0012 
0013 #include "tile.h"
0014 
0015 #define TILES_MAXSIZE 2000000
0016 
0017 using namespace Okular;
0018 
0019 static bool rankedTilesLessThan(TileNode *t1, TileNode *t2)
0020 {
0021     // Order tiles by its dirty state and then by distance from the viewport.
0022     if (t1->dirty == t2->dirty) {
0023         return t1->distance < t2->distance;
0024     }
0025 
0026     return !t1->dirty;
0027 }
0028 
0029 class TilesManager::Private
0030 {
0031 public:
0032     Private();
0033 
0034     bool hasPixmap(const NormalizedRect &rect, const TileNode &tile) const;
0035     void tilesAt(const NormalizedRect &rect, TileNode &tile, QList<Tile> &result, TileLeaf tileLeaf);
0036     void setPixmap(const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap);
0037 
0038     /**
0039      * Mark @p tile and all its children as dirty
0040      */
0041     static void markDirty(TileNode &tile);
0042 
0043     /**
0044      * Deletes all tiles, recursively
0045      */
0046     void deleteTiles(const TileNode &tile);
0047 
0048     void markParentDirty(const TileNode &tile);
0049     void rankTiles(TileNode &tile, QList<TileNode *> &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber);
0050     /**
0051      * Since the tile can be large enough to occupy a significant amount of
0052      * space, they may be split in more tiles. This operation is performed
0053      * when the tiles of a certain region is requested and they are bigger
0054      * than an arbitrary value. Only tiles intersecting the desired region
0055      * are split. There's no need to do this for the entire page.
0056      */
0057     void split(TileNode &tile, const NormalizedRect &rect);
0058 
0059     /**
0060      * Checks whether the tile's size is bigger than an arbitrary value and
0061      * performs the split operation returning true.
0062      * Otherwise it just returns false, without performing any operation.
0063      */
0064     bool splitBigTiles(TileNode &tile, const NormalizedRect &rect);
0065 
0066     // The page is split in a 4x4 grid of tiles
0067     TileNode tiles[16];
0068     int width;
0069     int height;
0070     int pageNumber;
0071     qulonglong totalPixels;
0072     Rotation rotation;
0073     NormalizedRect visibleRect;
0074     NormalizedRect requestRect;
0075     int requestWidth;
0076     int requestHeight;
0077 };
0078 
0079 TilesManager::Private::Private()
0080     : width(0)
0081     , height(0)
0082     , pageNumber(0)
0083     , totalPixels(0)
0084     , rotation(Rotation0)
0085     , requestRect(NormalizedRect())
0086     , requestWidth(0)
0087     , requestHeight(0)
0088 {
0089 }
0090 
0091 TilesManager::TilesManager(int pageNumber, int width, int height, Rotation rotation)
0092     : d(new Private)
0093 {
0094     d->pageNumber = pageNumber;
0095     d->width = width;
0096     d->height = height;
0097     d->rotation = rotation;
0098 
0099     // The page is split in a 4x4 grid of tiles
0100     const double dim = 0.25;
0101     for (int i = 0; i < 16; ++i) {
0102         int x = i % 4;
0103         int y = i / 4;
0104         d->tiles[i].rect = NormalizedRect(x * dim, y * dim, x * dim + dim, y * dim + dim);
0105     }
0106 }
0107 
0108 TilesManager::~TilesManager()
0109 {
0110     for (const TileNode &tile : d->tiles) {
0111         d->deleteTiles(tile);
0112     }
0113 
0114     delete d;
0115 }
0116 
0117 void TilesManager::Private::deleteTiles(const TileNode &tile)
0118 {
0119     if (tile.pixmap) {
0120         totalPixels -= tile.pixmap->width() * tile.pixmap->height();
0121         delete tile.pixmap;
0122     }
0123 
0124     if (tile.nTiles > 0) {
0125         for (int i = 0; i < tile.nTiles; ++i) {
0126             deleteTiles(tile.tiles[i]);
0127         }
0128 
0129         delete[] tile.tiles;
0130     }
0131 }
0132 
0133 void TilesManager::setSize(int width, int height)
0134 {
0135     if (width == d->width && height == d->height) {
0136         return;
0137     }
0138 
0139     d->width = width;
0140     d->height = height;
0141 
0142     markDirty();
0143 }
0144 
0145 int TilesManager::width() const
0146 {
0147     return d->width;
0148 }
0149 
0150 int TilesManager::height() const
0151 {
0152     return d->height;
0153 }
0154 
0155 void TilesManager::setRotation(Rotation rotation)
0156 {
0157     if (rotation == d->rotation) {
0158         return;
0159     }
0160 
0161     d->rotation = rotation;
0162 }
0163 
0164 Rotation TilesManager::rotation() const
0165 {
0166     return d->rotation;
0167 }
0168 
0169 void TilesManager::markDirty()
0170 {
0171     for (TileNode &tile : d->tiles) {
0172         TilesManager::Private::markDirty(tile);
0173     }
0174 }
0175 
0176 void TilesManager::Private::markDirty(TileNode &tile)
0177 {
0178     tile.dirty = true;
0179 
0180     for (int i = 0; i < tile.nTiles; ++i) {
0181         markDirty(tile.tiles[i]);
0182     }
0183 }
0184 
0185 void TilesManager::setPixmap(const QPixmap *pixmap, const NormalizedRect &rect, bool isPartialPixmap)
0186 {
0187     const NormalizedRect rotatedRect = TilesManager::fromRotatedRect(rect, d->rotation);
0188     if (!d->requestRect.isNull()) {
0189         if (!(d->requestRect == rect)) {
0190             return;
0191         }
0192 
0193         if (pixmap) {
0194             // Check whether the pixmap has the same absolute size of the expected
0195             // request.
0196             // If the document is rotated, rotate requestRect back to the original
0197             // rotation before comparing to pixmap's size. This is to avoid
0198             // conversion issues. The pixmap request was made using an unrotated
0199             // rect.
0200             QSize pixmapSize = pixmap->size();
0201             int w = width();
0202             int h = height();
0203             if (d->rotation % 2) {
0204                 std::swap(w, h);
0205                 pixmapSize.transpose();
0206             }
0207 
0208             if (rotatedRect.geometry(w, h).size() != pixmapSize) {
0209                 return;
0210             }
0211         }
0212 
0213         d->requestRect = NormalizedRect();
0214     }
0215 
0216     for (TileNode &tile : d->tiles) {
0217         d->setPixmap(pixmap, rotatedRect, tile, isPartialPixmap);
0218     }
0219 }
0220 
0221 void TilesManager::Private::setPixmap(const QPixmap *pixmap, const NormalizedRect &rect, TileNode &tile, bool isPartialPixmap)
0222 {
0223     QRect pixmapRect = TilesManager::toRotatedRect(rect, rotation).geometry(width, height);
0224 
0225     // Exclude tiles outside the viewport
0226     if (!tile.rect.intersects(rect)) {
0227         return;
0228     }
0229     // Avoid painting partial pixmaps over tiles that already have a fully rendered pixmap, even if dirty
0230     if (isPartialPixmap && tile.pixmap != nullptr && !tile.partial) {
0231         return;
0232     }
0233 
0234     // if the tile is not entirely within the viewport (the tile intersects an
0235     // edged of the viewport), attempt to set the pixmap in the children tiles
0236     if (!((tile.rect & rect) == tile.rect)) {
0237         // paint children tiles
0238         if (tile.nTiles > 0) {
0239             for (int i = 0; i < tile.nTiles; ++i) {
0240                 setPixmap(pixmap, rect, tile.tiles[i], isPartialPixmap);
0241             }
0242 
0243             delete tile.pixmap;
0244             tile.pixmap = nullptr;
0245         }
0246         // We could paint the pixmap over part of the tile here, but
0247         // there is little reason to as it will usually be offscreen
0248         // and it will be overwritten later if more tiles enter the screen,
0249         // as we only track the dirty state of whole tiles, not rects.
0250         return;
0251     }
0252 
0253     // the tile lies entirely within the viewport
0254     if (tile.nTiles == 0) {
0255         tile.dirty = isPartialPixmap;
0256         tile.partial = isPartialPixmap;
0257 
0258         // check whether the tile size is big and split it if necessary
0259         if (!splitBigTiles(tile, rect)) {
0260             if (tile.pixmap) {
0261                 totalPixels -= tile.pixmap->width() * tile.pixmap->height();
0262                 delete tile.pixmap;
0263             }
0264             tile.rotation = rotation;
0265             if (pixmap) {
0266                 const NormalizedRect rotatedRect = TilesManager::toRotatedRect(tile.rect, rotation);
0267                 tile.pixmap = new QPixmap(pixmap->copy(rotatedRect.geometry(width, height).translated(-pixmapRect.topLeft())));
0268                 totalPixels += tile.pixmap->width() * tile.pixmap->height();
0269             } else {
0270                 tile.pixmap = nullptr;
0271             }
0272         } else {
0273             if (tile.pixmap) {
0274                 totalPixels -= tile.pixmap->width() * tile.pixmap->height();
0275                 delete tile.pixmap;
0276                 tile.pixmap = nullptr;
0277             }
0278 
0279             for (int i = 0; i < tile.nTiles; ++i) {
0280                 setPixmap(pixmap, rect, tile.tiles[i], isPartialPixmap);
0281             }
0282         }
0283     } else {
0284         QRect tileRect = tile.rect.geometry(width, height);
0285         // sets the pixmap of the children tiles. if the tile's size is too
0286         // small, discards the children tiles and use the current one
0287         // Never join small tiles during a partial update in order to
0288         // not lose existing image data
0289         if (tileRect.width() * tileRect.height() >= TILES_MAXSIZE || isPartialPixmap) {
0290             tile.dirty = isPartialPixmap;
0291             tile.partial = isPartialPixmap;
0292             if (tile.pixmap) {
0293                 totalPixels -= tile.pixmap->width() * tile.pixmap->height();
0294                 delete tile.pixmap;
0295                 tile.pixmap = nullptr;
0296             }
0297 
0298             for (int i = 0; i < tile.nTiles; ++i) {
0299                 setPixmap(pixmap, rect, tile.tiles[i], isPartialPixmap);
0300             }
0301         } else {
0302             // remove children tiles
0303             for (int i = 0; i < tile.nTiles; ++i) {
0304                 deleteTiles(tile.tiles[i]);
0305                 tile.tiles[i].pixmap = nullptr;
0306             }
0307 
0308             delete[] tile.tiles;
0309             tile.tiles = nullptr;
0310             tile.nTiles = 0;
0311 
0312             // paint tile
0313             if (tile.pixmap) {
0314                 totalPixels -= tile.pixmap->width() * tile.pixmap->height();
0315                 delete tile.pixmap;
0316             }
0317             tile.rotation = rotation;
0318             if (pixmap) {
0319                 const NormalizedRect rotatedRect = TilesManager::toRotatedRect(tile.rect, rotation);
0320                 tile.pixmap = new QPixmap(pixmap->copy(rotatedRect.geometry(width, height).translated(-pixmapRect.topLeft())));
0321                 totalPixels += tile.pixmap->width() * tile.pixmap->height();
0322             } else {
0323                 tile.pixmap = nullptr;
0324             }
0325             tile.dirty = isPartialPixmap;
0326             tile.partial = isPartialPixmap;
0327         }
0328     }
0329 }
0330 
0331 bool TilesManager::hasPixmap(const NormalizedRect &rect)
0332 {
0333     NormalizedRect rotatedRect = fromRotatedRect(rect, d->rotation);
0334     for (const TileNode &tile : std::as_const(d->tiles)) {
0335         if (!d->hasPixmap(rotatedRect, tile)) {
0336             return false;
0337         }
0338     }
0339 
0340     return true;
0341 }
0342 
0343 bool TilesManager::Private::hasPixmap(const NormalizedRect &rect, const TileNode &tile) const
0344 {
0345     const NormalizedRect rectIntersection = tile.rect & rect;
0346     if (rectIntersection.width() <= 0 || rectIntersection.height() <= 0) {
0347         return true;
0348     }
0349 
0350     if (tile.nTiles == 0) {
0351         return tile.isValid();
0352     }
0353 
0354     // all children tiles are clean. doesn't need to go deeper
0355     if (!tile.dirty) {
0356         return true;
0357     }
0358 
0359     for (int i = 0; i < tile.nTiles; ++i) {
0360         if (!hasPixmap(rect, tile.tiles[i])) {
0361             return false;
0362         }
0363     }
0364 
0365     return true;
0366 }
0367 
0368 QList<Tile> TilesManager::tilesAt(const NormalizedRect &rect, TileLeaf tileLeaf)
0369 {
0370     QList<Tile> result;
0371 
0372     NormalizedRect rotatedRect = fromRotatedRect(rect, d->rotation);
0373     for (TileNode &tile : d->tiles) {
0374         d->tilesAt(rotatedRect, tile, result, tileLeaf);
0375     }
0376 
0377     return result;
0378 }
0379 
0380 void TilesManager::Private::tilesAt(const NormalizedRect &rect, TileNode &tile, QList<Tile> &result, TileLeaf tileLeaf)
0381 {
0382     if (!tile.rect.intersects(rect)) {
0383         return;
0384     }
0385 
0386     // split big tiles before the requests are made, otherwise we would end up
0387     // requesting huge areas unnecessarily
0388     splitBigTiles(tile, rect);
0389 
0390     if ((tileLeaf == TerminalTile && tile.nTiles == 0) || (tileLeaf == PixmapTile && tile.pixmap)) {
0391         NormalizedRect rotatedRect;
0392         if (rotation != Rotation0) {
0393             rotatedRect = TilesManager::toRotatedRect(tile.rect, rotation);
0394         } else {
0395             rotatedRect = tile.rect;
0396         }
0397 
0398         if (tile.pixmap && tileLeaf == PixmapTile && tile.rotation != rotation) {
0399             // Lazy tiles rotation
0400             int angleToRotate = (rotation - tile.rotation) * 90;
0401             int xOffset = 0, yOffset = 0;
0402             int w = 0, h = 0;
0403             switch (angleToRotate) {
0404             case 0:
0405                 xOffset = 0;
0406                 yOffset = 0;
0407                 w = tile.pixmap->width();
0408                 h = tile.pixmap->height();
0409                 break;
0410             case 90:
0411             case -270:
0412                 xOffset = 0;
0413                 yOffset = -tile.pixmap->height();
0414                 w = tile.pixmap->height();
0415                 h = tile.pixmap->width();
0416                 break;
0417             case 180:
0418             case -180:
0419                 xOffset = -tile.pixmap->width();
0420                 yOffset = -tile.pixmap->height();
0421                 w = tile.pixmap->width();
0422                 h = tile.pixmap->height();
0423                 break;
0424             case 270:
0425             case -90:
0426                 xOffset = -tile.pixmap->width();
0427                 yOffset = 0;
0428                 w = tile.pixmap->height();
0429                 h = tile.pixmap->width();
0430                 break;
0431             }
0432             QPixmap *rotatedPixmap = new QPixmap(w, h);
0433             QPainter p(rotatedPixmap);
0434             p.rotate(angleToRotate);
0435             p.translate(xOffset, yOffset);
0436             p.drawPixmap(0, 0, *tile.pixmap);
0437             p.end();
0438 
0439             delete tile.pixmap;
0440             tile.pixmap = rotatedPixmap;
0441             tile.rotation = rotation;
0442         }
0443         result.append(Tile(rotatedRect, tile.pixmap, tile.isValid()));
0444     } else {
0445         for (int i = 0; i < tile.nTiles; ++i) {
0446             tilesAt(rect, tile.tiles[i], result, tileLeaf);
0447         }
0448     }
0449 }
0450 
0451 qulonglong TilesManager::totalMemory() const
0452 {
0453     return 4 * d->totalPixels;
0454 }
0455 
0456 void TilesManager::cleanupPixmapMemory(qulonglong numberOfBytes, const NormalizedRect &visibleRect, int visiblePageNumber)
0457 {
0458     QList<TileNode *> rankedTiles;
0459     for (TileNode &tile : d->tiles) {
0460         d->rankTiles(tile, rankedTiles, visibleRect, visiblePageNumber);
0461     }
0462     std::sort(rankedTiles.begin(), rankedTiles.end(), rankedTilesLessThan);
0463 
0464     while (numberOfBytes > 0 && !rankedTiles.isEmpty()) {
0465         TileNode *tile = rankedTiles.takeLast();
0466         if (!tile->pixmap) {
0467             continue;
0468         }
0469 
0470         // do not evict visible pixmaps
0471         if (tile->rect.intersects(visibleRect)) {
0472             continue;
0473         }
0474 
0475         qulonglong pixels = tile->pixmap->width() * tile->pixmap->height();
0476         d->totalPixels -= pixels;
0477         if (numberOfBytes < 4 * pixels) {
0478             numberOfBytes = 0;
0479         } else {
0480             numberOfBytes -= 4 * pixels;
0481         }
0482 
0483         delete tile->pixmap;
0484         tile->pixmap = nullptr;
0485 
0486         tile->partial = true;
0487 
0488         d->markParentDirty(*tile);
0489     }
0490 }
0491 
0492 void TilesManager::Private::markParentDirty(const TileNode &tile)
0493 {
0494     if (!tile.parent) {
0495         return;
0496     }
0497 
0498     if (!tile.parent->dirty) {
0499         tile.parent->dirty = true;
0500         markParentDirty(*tile.parent);
0501     }
0502 }
0503 
0504 void TilesManager::Private::rankTiles(TileNode &tile, QList<TileNode *> &rankedTiles, const NormalizedRect &visibleRect, int visiblePageNumber)
0505 {
0506     // If the page is visible, visibleRect is not null.
0507     // Otherwise we use the number of one of the visible pages to calculate the
0508     // distance.
0509     // Note that the current page may be visible and yet its pageNumber is
0510     // different from visiblePageNumber. Since we only use this value on hidden
0511     // pages, any visible page number will fit.
0512     if (visibleRect.isNull() && visiblePageNumber < 0) {
0513         return;
0514     }
0515 
0516     if (tile.pixmap) {
0517         // Update distance
0518         if (!visibleRect.isNull()) {
0519             NormalizedPoint viewportCenter = visibleRect.center();
0520             NormalizedPoint tileCenter = tile.rect.center();
0521             // Manhattan distance. It's a good and fast approximation.
0522             tile.distance = qAbs(viewportCenter.x - tileCenter.x) + qAbs(viewportCenter.y - tileCenter.y);
0523         } else {
0524             // For non visible pages only the vertical distance is used
0525             if (pageNumber < visiblePageNumber) {
0526                 tile.distance = 1 - tile.rect.bottom;
0527             } else {
0528                 tile.distance = tile.rect.top;
0529             }
0530         }
0531         rankedTiles.append(&tile);
0532     } else {
0533         for (int i = 0; i < tile.nTiles; ++i) {
0534             rankTiles(tile.tiles[i], rankedTiles, visibleRect, visiblePageNumber);
0535         }
0536     }
0537 }
0538 
0539 bool TilesManager::isRequesting(const NormalizedRect &rect, int pageWidth, int pageHeight) const
0540 {
0541     return rect == d->requestRect && pageWidth == d->requestWidth && pageHeight == d->requestHeight;
0542 }
0543 
0544 void TilesManager::setRequest(const NormalizedRect &rect, int pageWidth, int pageHeight)
0545 {
0546     d->requestRect = rect;
0547     d->requestWidth = pageWidth;
0548     d->requestHeight = pageHeight;
0549 }
0550 
0551 bool TilesManager::Private::splitBigTiles(TileNode &tile, const NormalizedRect &rect)
0552 {
0553     QRect tileRect = tile.rect.geometry(width, height);
0554     if (tileRect.width() * tileRect.height() < TILES_MAXSIZE) {
0555         return false;
0556     }
0557 
0558     split(tile, rect);
0559     return true;
0560 }
0561 
0562 void TilesManager::Private::split(TileNode &tile, const NormalizedRect &rect)
0563 {
0564     if (tile.nTiles != 0) {
0565         return;
0566     }
0567 
0568     if (rect.isNull() || !tile.rect.intersects(rect)) {
0569         return;
0570     }
0571 
0572     tile.nTiles = 4;
0573     tile.tiles = new TileNode[4];
0574     double hCenter = (tile.rect.left + tile.rect.right) / 2;
0575     double vCenter = (tile.rect.top + tile.rect.bottom) / 2;
0576 
0577     tile.tiles[0].rect = NormalizedRect(tile.rect.left, tile.rect.top, hCenter, vCenter);
0578     tile.tiles[1].rect = NormalizedRect(hCenter, tile.rect.top, tile.rect.right, vCenter);
0579     tile.tiles[2].rect = NormalizedRect(tile.rect.left, vCenter, hCenter, tile.rect.bottom);
0580     tile.tiles[3].rect = NormalizedRect(hCenter, vCenter, tile.rect.right, tile.rect.bottom);
0581 
0582     for (int i = 0; i < tile.nTiles; ++i) {
0583         tile.tiles[i].parent = &tile;
0584         splitBigTiles(tile.tiles[i], rect);
0585     }
0586 }
0587 
0588 NormalizedRect TilesManager::fromRotatedRect(const NormalizedRect &rect, Rotation rotation)
0589 {
0590     if (rotation == Rotation0) {
0591         return rect;
0592     }
0593 
0594     NormalizedRect newRect;
0595     switch (rotation) {
0596     case Rotation90:
0597         newRect = NormalizedRect(rect.top, 1 - rect.right, rect.bottom, 1 - rect.left);
0598         break;
0599     case Rotation180:
0600         newRect = NormalizedRect(1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top);
0601         break;
0602     case Rotation270:
0603         newRect = NormalizedRect(1 - rect.bottom, rect.left, 1 - rect.top, rect.right);
0604         break;
0605     default:
0606         newRect = rect;
0607         break;
0608     }
0609 
0610     return newRect;
0611 }
0612 
0613 NormalizedRect TilesManager::toRotatedRect(const NormalizedRect &rect, Rotation rotation)
0614 {
0615     if (rotation == Rotation0) {
0616         return rect;
0617     }
0618 
0619     NormalizedRect newRect;
0620     switch (rotation) {
0621     case Rotation90:
0622         newRect = NormalizedRect(1 - rect.bottom, rect.left, 1 - rect.top, rect.right);
0623         break;
0624     case Rotation180:
0625         newRect = NormalizedRect(1 - rect.right, 1 - rect.bottom, 1 - rect.left, 1 - rect.top);
0626         break;
0627     case Rotation270:
0628         newRect = NormalizedRect(rect.top, 1 - rect.right, rect.bottom, 1 - rect.left);
0629         break;
0630     default:
0631         newRect = rect;
0632         break;
0633     }
0634 
0635     return newRect;
0636 }
0637 
0638 TileNode::TileNode()
0639     : pixmap(nullptr)
0640     , rotation(Rotation0)
0641     , dirty(true)
0642     , partial(true)
0643     , distance(-1)
0644     , tiles(nullptr)
0645     , nTiles(0)
0646     , parent(nullptr)
0647 {
0648 }
0649 
0650 bool TileNode::isValid() const
0651 {
0652     return pixmap && !dirty;
0653 }
0654 
0655 class Tile::Private
0656 {
0657 public:
0658     Private();
0659 
0660     NormalizedRect rect;
0661     QPixmap *pixmap;
0662     bool isValid;
0663 };
0664 
0665 Tile::Private::Private()
0666     : pixmap(nullptr)
0667     , isValid(false)
0668 {
0669 }
0670 
0671 Tile::Tile(const NormalizedRect &rect, QPixmap *pixmap, bool isValid)
0672     : d(new Tile::Private)
0673 {
0674     d->rect = rect;
0675     d->pixmap = pixmap;
0676     d->isValid = isValid;
0677 }
0678 
0679 Tile::Tile(const Tile &t)
0680     : d(new Tile::Private)
0681 {
0682     d->rect = t.d->rect;
0683     d->pixmap = t.d->pixmap;
0684     d->isValid = t.d->isValid;
0685 }
0686 
0687 Tile &Tile::operator=(const Tile &other)
0688 {
0689     if (this == &other) {
0690         return *this;
0691     }
0692 
0693     d->rect = other.d->rect;
0694     d->pixmap = other.d->pixmap;
0695     d->isValid = other.d->isValid;
0696 
0697     return *this;
0698 }
0699 
0700 Tile::~Tile()
0701 {
0702     delete d;
0703 }
0704 
0705 NormalizedRect Tile::rect() const
0706 {
0707     return d->rect;
0708 }
0709 
0710 QPixmap *Tile::pixmap() const
0711 {
0712     return d->pixmap;
0713 }
0714 
0715 bool Tile::isValid() const
0716 {
0717     return d->isValid;
0718 }