diff --git a/app/qml/ui/ToolBar.qml b/app/qml/ui/ToolBar.qml index 09545f23..99472cf8 100644 --- a/app/qml/ui/ToolBar.qml +++ b/app/qml/ui/ToolBar.qml @@ -297,7 +297,6 @@ ToolBar { objectName: "toolShapeButton" hoverEnabled: true focusPolicy: Qt.NoFocus - visible: projectType === Project.ImageType || projectType === Project.LayeredImageType readonly property bool squareShape: canvas && canvas.toolShape === ImageCanvas.SquareToolShape icon.source: squareShape ? "qrc:/images/square-tool-shape.png" : "qrc:/images/circle-tool-shape.png" diff --git a/lib/applypixellinecommand.cpp b/lib/applypixellinecommand.cpp index f7ff5889..67d980c3 100644 --- a/lib/applypixellinecommand.cpp +++ b/lib/applypixellinecommand.cpp @@ -30,20 +30,38 @@ Q_LOGGING_CATEGORY(lcApplyPixelLineCommand, "app.undo.applyPixelLineCommand") // The undo command for lines needs the project image before and after // the line was drawn on it. ApplyPixelLineCommand::ApplyPixelLineCommand(ImageCanvas *canvas, int layerIndex, QImage ¤tProjectImage, const QPointF point1, const QPointF point2, - const QPoint &newLastPixelPenReleaseScenePos, const QPoint &oldLastPixelPenReleaseScenePos, const QPainter::CompositionMode mode, QUndoCommand *parent) : + const QPointF &newLastPixelPenReleaseScenePos, const QPointF &oldLastPixelPenReleaseScenePos, + const QPainter::CompositionMode mode, QUndoCommand *parent) : QUndoCommand(parent), mCanvas(canvas), mLayerIndex(layerIndex), - mImageWithLine(currentProjectImage), - mLineRect(mCanvas->normalisedLineRect(point1, point2)), - mImageWithoutLine(currentProjectImage.copy(mLineRect)), mNewLastPixelPenReleaseScenePos(newLastPixelPenReleaseScenePos), - mOldLastPixelPenReleaseScenePos(oldLastPixelPenReleaseScenePos) + mOldLastPixelPenReleaseScenePos(oldLastPixelPenReleaseScenePos), + subImageDatas() { - QPainter painter(&mImageWithLine); - mCanvas->drawLine(&painter, point1, point2, mode); - painter.end(); - mImageWithLine = mImageWithLine.copy(mLineRect); + const QRect lineRect = mCanvas->normalisedLineRect(point1, point2); + const QList subImages = canvas->subImagesInBounds(lineRect); + for (auto const &subImage : subImages) { + // subimage-space to scene-space offset + const QPoint offset = subImage.bounds.topLeft() - subImage.offset; + // line rect offset to scene space and clipped to subimage bounds + const QRect subImageLineRect = subImage.bounds.intersected(lineRect.translated(offset)); + + SubImageData subImageData; + subImageData.subImage = subImage; + subImageData.lineRect = subImageLineRect; + subImageData.imageWithoutLine = currentProjectImage.copy(subImageLineRect); + + QPainter painter(¤tProjectImage); + // Clip drawing to subimage + painter.setClipRect(subImageLineRect); + // Draw line with offset to subimage + mCanvas->drawLine(&painter, point1 + offset, point2 + offset, mode); + painter.end(); + + subImageData.imageWithLine = currentProjectImage.copy(subImageLineRect); + subImageDatas.append(subImageData); + } qCDebug(lcApplyPixelLineCommand) << "constructed" << this; } @@ -56,13 +74,17 @@ ApplyPixelLineCommand::~ApplyPixelLineCommand() void ApplyPixelLineCommand::undo() { qCDebug(lcApplyPixelLineCommand) << "undoing" << this; - mCanvas->applyPixelLineTool(mLayerIndex, mImageWithoutLine, mLineRect, mOldLastPixelPenReleaseScenePos); + for (auto const &subImageData : subImageDatas) { + mCanvas->applyPixelLineTool(mLayerIndex, subImageData.imageWithoutLine, subImageData.lineRect, mOldLastPixelPenReleaseScenePos); + } } void ApplyPixelLineCommand::redo() { qCDebug(lcApplyPixelLineCommand) << "redoing" << this; - mCanvas->applyPixelLineTool(mLayerIndex, mImageWithLine, mLineRect, mNewLastPixelPenReleaseScenePos); + for (auto const &subImageData : subImageDatas) { + mCanvas->applyPixelLineTool(mLayerIndex, subImageData.imageWithLine, subImageData.lineRect, mNewLastPixelPenReleaseScenePos); + } } int ApplyPixelLineCommand::id() const @@ -79,7 +101,7 @@ QDebug operator<<(QDebug debug, const ApplyPixelLineCommand *command) { debug.nospace() << "(ApplyPixelLineCommand" << " layerIndex=" << command->mLayerIndex - << ", lineRect" << command->mLineRect +// << ", lineRect" << command->mLineRect << ", newLastPixelPenReleaseScenePos=" << command->mNewLastPixelPenReleaseScenePos << ", oldLastPixelPenReleaseScenePos=" << command->mOldLastPixelPenReleaseScenePos << ")"; diff --git a/lib/applypixellinecommand.h b/lib/applypixellinecommand.h index ad458319..783d7f6f 100644 --- a/lib/applypixellinecommand.h +++ b/lib/applypixellinecommand.h @@ -30,8 +30,9 @@ class SLATE_EXPORT ApplyPixelLineCommand : public QUndoCommand { public: - ApplyPixelLineCommand(ImageCanvas *canvas, int layerIndex, QImage ¤tProjectImage, const QPointF point1, const QPointF point2, const QPoint &newLastPixelPenReleaseScenePos, - const QPoint &oldLastPixelPenReleaseScenePos, const QPainter::CompositionMode mode, QUndoCommand *parent = nullptr); + ApplyPixelLineCommand(ImageCanvas *canvas, int layerIndex, QImage ¤tProjectImage, const QPointF point1, const QPointF point2, + const QPointF &newLastPixelPenReleaseScenePos, const QPointF &oldLastPixelPenReleaseScenePos, + const QPainter::CompositionMode mode, QUndoCommand *parent = nullptr); ~ApplyPixelLineCommand(); void undo() override; @@ -45,11 +46,16 @@ class SLATE_EXPORT ApplyPixelLineCommand : public QUndoCommand ImageCanvas *mCanvas; int mLayerIndex; - QImage mImageWithLine; - QRect mLineRect; - QImage mImageWithoutLine; - QPoint mNewLastPixelPenReleaseScenePos; - QPoint mOldLastPixelPenReleaseScenePos; + QPointF mNewLastPixelPenReleaseScenePos; + QPointF mOldLastPixelPenReleaseScenePos; + + struct SubImageData { + ImageCanvas::SubImage subImage; + QRect lineRect; + QImage imageWithoutLine; + QImage imageWithLine; + }; + QList subImageDatas; }; diff --git a/lib/imagecanvas.cpp b/lib/imagecanvas.cpp index 7d756406..703f05de 100644 --- a/lib/imagecanvas.cpp +++ b/lib/imagecanvas.cpp @@ -822,6 +822,15 @@ qreal ImageCanvas::lineAngle() const return line.angle(); } +QList ImageCanvas::subImagesInBounds(const QRect &bounds) const +{ + QList subImages; + if (bounds.intersects(mProject->bounds())) { + subImages.append(SubImage{mProject->bounds(), {}}); + } + return subImages; +} + void ImageCanvas::setAltPressed(bool altPressed) { if (altPressed == mAltPressed) @@ -982,7 +991,7 @@ QImage ImageCanvas::getContentImage() return image; } -void ImageCanvas::drawLine(QPainter *painter, const QPointF point1, const QPointF point2, const QPainter::CompositionMode mode) const +void ImageCanvas::drawLine(QPainter *painter, QPointF point1, QPointF point2, const QPainter::CompositionMode mode) const { painter->save(); @@ -999,11 +1008,27 @@ void ImageCanvas::drawLine(QPainter *painter, const QPointF point1, const QPoint } painter->setPen(pen); - QLineF line(point1, point2); + // Offset odd sized pens to pixel centre to centre pen + const QPointF penOffset = (mToolSize % 2 == 1) ? QPointF(0.5, 0.5) : QPointF(0.0, 0.0); + + // Snap points to points to pixel grid + if (mToolSize > 1) { + point1 = (point1 + penOffset).toPoint() - penOffset; + point2 = (point2 + penOffset).toPoint() - penOffset; + } + else { + // Handle inconsitant width 1 pen behaviour, off pixel centres so results in non-ideal asymetrical lines but + // would require either redrawing previous segment as part of stroke or custom line function to prevent spurs + point1 = QPointF(qFloor(point1.x()), qFloor(point1.y())); + point2 = QPointF(qFloor(point2.x()), qFloor(point2.y())); + } + + const QLineF line(point1, point2); + painter->setCompositionMode(mode); - // Zero-length line doesn't draw so handle case with drawPoint - if (line.length() == 0.0) { - painter->drawPoint(point1); + // Zero-length line doesn't draw with round pen so handle case with drawPoint + if (line.p1() == line.p2()) { + painter->drawPoint(line.p1()); } else { painter->drawLine(line); @@ -1648,6 +1673,7 @@ void ImageCanvas::reset() mLastMouseButtonPressed = Qt::NoButton; mPressPosition = QPoint(0, 0); mPressScenePosition = QPoint(0, 0); + mPressScenePositionF = QPointF(0.0, 0.0); mCurrentPaneOffsetBeforePress = QPoint(0, 0); mFirstPaneVisibleSceneArea = QRect(); mSecondPaneVisibleSceneArea = QRect(); @@ -1938,7 +1964,7 @@ void ImageCanvas::applyCurrentTool() // This ensures that e.g. a translucent red overwrites whatever pixels it // lies on, rather than blending with them. mProject->addChange(new ApplyPixelLineCommand(this, mProject->currentLayerIndex(), *currentProjectImage(), linePoint1(), linePoint2(), - mPressScenePosition, mLastPixelPenPressScenePosition, QPainter::CompositionMode_Source)); + mPressScenePositionF, mLastPixelPenPressScenePositionF, QPainter::CompositionMode_Source)); break; } case EyeDropperTool: { @@ -1952,7 +1978,7 @@ void ImageCanvas::applyCurrentTool() mProject->beginMacro(QLatin1String("PixelEraserTool")); // Draw the line on top of what has already been painted using a special composition mode to erase pixels. mProject->addChange(new ApplyPixelLineCommand(this, mProject->currentLayerIndex(), *currentProjectImage(), linePoint1(), linePoint2(), - mPressScenePosition, mLastPixelPenPressScenePosition, QPainter::CompositionMode_Clear)); + mPressScenePositionF, mLastPixelPenPressScenePositionF, QPainter::CompositionMode_Clear)); break; } case FillTool: { @@ -2015,9 +2041,9 @@ void ImageCanvas::applyPixelPenTool(int layerIndex, const QPoint &scenePos, cons } void ImageCanvas::applyPixelLineTool(int layerIndex, const QImage &lineImage, const QRect &lineRect, - const QPoint &lastPixelPenReleaseScenePosition) + const QPointF &lastPixelPenReleaseScenePosition) { - mLastPixelPenPressScenePosition = lastPixelPenReleaseScenePosition; + mLastPixelPenPressScenePositionF = lastPixelPenReleaseScenePosition; QPainter painter(imageForLayerAt(layerIndex)); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(lineRect, lineImage); @@ -2076,12 +2102,12 @@ QRect ImageCanvas::doRotateSelection(int layerIndex, const QRect &area, int angl QPointF ImageCanvas::linePoint1() const { - return mLastPixelPenPressScenePosition; + return mLastPixelPenPressScenePositionF; } QPointF ImageCanvas::linePoint2() const { - return QPointF(mCursorSceneX, mCursorSceneY); + return QPointF(mCursorSceneFX, mCursorSceneFY); } QRect ImageCanvas::normalisedLineRect(const QPointF point1, const QPointF point2) const @@ -2090,7 +2116,7 @@ QRect ImageCanvas::normalisedLineRect(const QPointF point1, const QPointF point2 // a simplification of Pythagoras’ theorem. // The bounds could be tighter by taking into account the specific rotation of the brush, // but the sqrt(2) ensures it is big enough for any rotation. - const int margin = qCeil(M_SQRT2 * mToolSize / 2.0); + const int margin = qCeil(M_SQRT2 * mToolSize / 2.0) + 1; return QRect(point1.toPoint(), point2.toPoint()).normalized() .marginsAdded({margin, margin, margin, margin}); } @@ -2483,6 +2509,7 @@ void ImageCanvas::mousePressEvent(QMouseEvent *event) mLastMouseButtonPressed = mMouseButtonPressed; mPressPosition = event->pos(); mPressScenePosition = QPoint(mCursorSceneX, mCursorSceneY); + mPressScenePositionF = QPointF(mCursorSceneFX, mCursorSceneFY); mCurrentPaneOffsetBeforePress = mCurrentPane->integerOffset(); setContainsMouse(true); @@ -2507,7 +2534,7 @@ void ImageCanvas::mousePressEvent(QMouseEvent *event) } if (!mShiftPressed) { - mLastPixelPenPressScenePosition = mPressScenePosition; + mLastPixelPenPressScenePositionF = mPressScenePositionF; } if (mTool != SelectionTool) { @@ -2533,7 +2560,7 @@ void ImageCanvas::mouseMoveEvent(QMouseEvent *event) { QQuickItem::mouseMoveEvent(event); - const QPoint oldCursorScenePosition = QPoint(mCursorSceneX, mCursorSceneY); + const QPointF oldCursorScenePosition = QPointF(mCursorSceneFX, mCursorSceneFY); updateCursorPos(event->pos()); if (!mProject->hasLoaded()) @@ -2551,11 +2578,12 @@ void ImageCanvas::mouseMoveEvent(QMouseEvent *event) } else if (mPressedGuideIndex != -1) { mGuidesItem->update(); } else { - if (!isPanning()) { + if (!isPanning()) { if (mTool != SelectionTool) { mPressScenePosition = QPoint(mCursorSceneX, mCursorSceneY); + mPressScenePositionF = QPointF(mCursorSceneFX, mCursorSceneFY); if (!mShiftPressed) { - mLastPixelPenPressScenePosition = oldCursorScenePosition; + mLastPixelPenPressScenePositionF = oldCursorScenePosition; } applyCurrentTool(); } else { diff --git a/lib/imagecanvas.h b/lib/imagecanvas.h index 5228661c..8a513a6b 100644 --- a/lib/imagecanvas.h +++ b/lib/imagecanvas.h @@ -234,6 +234,17 @@ class SLATE_EXPORT ImageCanvas : public QQuickItem int lineLength() const; qreal lineAngle() const; + struct SubImage { + bool operator==(const SubImage &other) const { + return bounds == other.bounds && offset == other.offset; + } + + QRect bounds; + QPoint offset; + }; + + virtual QList subImagesInBounds(const QRect &bounds) const; + // Essentially currentProjectImage() for regular image canvas, but may return a // preview image if there is a selection active. For layered image canvases, this // should return all layers flattened into one image, or the same flattened image @@ -375,7 +386,7 @@ protected slots: virtual void applyCurrentTool(); virtual void applyPixelPenTool(int layerIndex, const QPoint &scenePos, const QColor &colour, bool markAsLastRelease = false); - virtual void applyPixelLineTool(int layerIndex, const QImage &lineImage, const QRect &lineRect, const QPoint &lastPixelPenReleaseScenePosition); + virtual void applyPixelLineTool(int layerIndex, const QImage &lineImage, const QRect &lineRect, const QPointF &lastPixelPenReleaseScenePosition); void paintImageOntoPortionOfImage(int layerIndex, const QRect &portion, const QImage &replacementImage); void replacePortionOfImage(int layerIndex, const QRect &portion, const QImage &replacementImage); void erasePortionOfImage(int layerIndex, const QRect &portion); @@ -405,7 +416,7 @@ protected slots: CanvasPane *hoveredPane(const QPoint &pos); QPoint eventPosRelativeToCurrentPane(const QPoint &pos); virtual QImage getContentImage(); - void drawLine(QPainter *painter, const QPointF point1, const QPointF point2, const QPainter::CompositionMode mode) const; + void drawLine(QPainter *painter, QPointF point1, QPointF point2, const QPainter::CompositionMode mode) const; void centrePanes(bool respectSceneCentred = true); enum ResetPaneSizePolicy { DontResetPaneSizes, @@ -540,6 +551,7 @@ protected slots: // The position at which the mouse is currently pressed. QPoint mPressPosition; QPoint mPressScenePosition; + QPointF mPressScenePositionF; // The scene position at which the mouse was pressed before the most-recent press. QPoint mCurrentPaneOffsetBeforePress; QRect mFirstPaneVisibleSceneArea; @@ -562,6 +574,7 @@ protected slots: // It is set by the pixel tool as the last pixel in the command, // and by the pixel line tool command. QPoint mLastPixelPenPressScenePosition; + QPointF mLastPixelPenPressScenePositionF; // An image as large as the rectangle that contains the line that is being previewed. QImage mLinePreviewImage; @@ -599,4 +612,16 @@ protected slots: bool mHasBlankCursor; }; +inline uint qHash(const ImageCanvas::SubImage &key, const uint seed = 0) { + return qHashBits(&key, sizeof(ImageCanvas::SubImage), seed); +} + +inline QDebug operator<<(QDebug debug, const ImageCanvas::SubImage &subImage) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "SubImage(" << subImage.bounds << ", " << subImage.offset << ')'; + + return debug; +} + #endif // IMAGECANVAS_H diff --git a/lib/imageproject.h b/lib/imageproject.h index ab7abf22..391be335 100644 --- a/lib/imageproject.h +++ b/lib/imageproject.h @@ -44,7 +44,7 @@ class SLATE_EXPORT ImageProject : public Project void setSize(const QSize &newSize) override; int widthInPixels() const override; int heightInPixels() const override; - QRect bounds() const override; + virtual QRect bounds() const override; bool isUsingAnimation() const; void setUsingAnimation(bool isUsingAnimation); diff --git a/lib/tilecanvas.cpp b/lib/tilecanvas.cpp index 8e961992..f9e426ed 100644 --- a/lib/tilecanvas.cpp +++ b/lib/tilecanvas.cpp @@ -27,6 +27,7 @@ #include "applypixelerasercommand.h" #include "applypixelfillcommand.h" +#include "applypixellinecommand.h" #include "applypixelpencommand.h" #include "applytilecanvaspixelfillcommand.h" #include "applytileerasercommand.h" @@ -35,6 +36,7 @@ #include "fillalgorithms.h" #include "tileset.h" #include "tilesetproject.h" +#include "utils.h" TileCanvas::TileCanvas() : mTilesetProject(nullptr), @@ -320,13 +322,20 @@ void TileCanvas::applyCurrentTool() switch (mTool) { case PenTool: { if (mMode == PixelMode) { - const PixelCandidateData candidateData = penEraserPixelCandidates(mTool); - if (candidateData.scenePositions.isEmpty()) { - return; - } - - mTilesetProject->beginMacro(QLatin1String("PixelPenTool")); - mTilesetProject->addChange(new ApplyPixelPenCommand(this, -1, candidateData.scenePositions, candidateData.previousColours, penColour())); +// const PixelCandidateData candidateData = penEraserPixelCandidates(mTool); +// if (candidateData.scenePositions.isEmpty()) { +// return; +// } + +// mTilesetProject->beginMacro(QLatin1String("PixelPenTool")); +// mTilesetProject->addChange(new ApplyPixelPenCommand(this, -1, candidateData.scenePositions, candidateData.previousColours, penColour())); + mProject->beginMacro(QLatin1String("PixelLineTool")); + // Draw the line on top of what has already been painted using a special composition mode. + // This ensures that e.g. a translucent red overwrites whatever pixels it + // lies on, rather than blending with them. + mProject->addChange(new ApplyPixelLineCommand(this, -1, *mTilesetProject->tileset()->image(), linePoint1(), linePoint2(), + mPressScenePosition, mLastPixelPenPressScenePosition, QPainter::CompositionMode_Source)); + break; } else { const QPoint scenePos = QPoint(mCursorSceneX, mCursorSceneY); const Tile *tile = mTilesetProject->tileAt(scenePos); @@ -410,7 +419,28 @@ void TileCanvas::applyCurrentTool() QPoint TileCanvas::scenePosToTilePixelPos(const QPoint &scenePos) const { return QPoint(scenePos.x() % mTilesetProject->tileWidth(), - scenePos.y() % mTilesetProject->tileHeight()); + scenePos.y() % mTilesetProject->tileHeight()); +} + +QRect TileCanvas::sceneRectToTileRect(const QRect &sceneRect) const +{ + return QRect(QPoint(Utils::divFloor(sceneRect.left(), mTilesetProject->tileWidth()), Utils::divFloor(sceneRect.top(), mTilesetProject->tileHeight())), + QPoint(Utils::divFloor(sceneRect.right(), mTilesetProject->tileWidth()), Utils::divFloor(sceneRect.bottom(), mTilesetProject->tileHeight()))); +} + +QList TileCanvas::subImagesInBounds(const QRect &bounds) const +{ + const QRect tileRect = sceneRectToTileRect(bounds); + QList subImages; + for (int y = tileRect.top(); y <= tileRect.bottom(); ++y) { + for (int x = tileRect.left(); x <= tileRect.right(); ++x) { + const Tile *const tile = mTilesetProject->tileAtTilePos({x, y}); + if (tile) { + subImages.append({tile->sourceRect(), {x * mTilesetProject->tileWidth(), y * mTilesetProject->tileHeight()}}); + } + } + } + return subImages; } // This function actually operates on the image. @@ -435,6 +465,15 @@ void TileCanvas::applyTilePenTool(const QPoint &tilePos, int id) requestContentPaint(); } +void TileCanvas::applyPixelLineTool(int layerIndex, const QImage &lineImage, const QRect &lineRect, const QPointF &lastPixelPenReleaseScenePosition) +{ + mLastPixelPenPressScenePositionF = lastPixelPenReleaseScenePosition; + QPainter painter(mTilesetProject->tileset()->image()); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.drawImage(lineRect, lineImage); + requestContentPaint(); +} + void TileCanvas::updateCursorPos(const QPoint &eventPos) { setCursorX(eventPos.x()); diff --git a/lib/tilecanvas.h b/lib/tilecanvas.h index 865e8692..3b76d808 100644 --- a/lib/tilecanvas.h +++ b/lib/tilecanvas.h @@ -68,6 +68,9 @@ class SLATE_EXPORT TileCanvas : public ImageCanvas void setPenTile(Tile *penTile); QPoint scenePosToTilePixelPos(const QPoint &scenePos) const; + QRect sceneRectToTileRect(const QRect &sceneRect) const; + + virtual QList subImagesInBounds(const QRect &bounds) const override; signals: void cursorTilePixelXChanged(); @@ -122,6 +125,7 @@ protected slots: void applyCurrentTool() override; void applyPixelPenTool(int layerIndex, const QPoint &scenePos, const QColor &colour, bool markAsLastRelease = false) override; void applyTilePenTool(const QPoint &tilePos, int id); + void applyPixelLineTool(int layerIndex, const QImage &lineImage, const QRect &lineRect, const QPointF &lastPixelPenReleaseScenePosition) override; void updateCursorPos(const QPoint &eventPos) override; void error(const QString &message); diff --git a/lib/tileset.cpp b/lib/tileset.cpp index 48b12aa0..8b508538 100644 --- a/lib/tileset.cpp +++ b/lib/tileset.cpp @@ -56,6 +56,11 @@ const QImage *Tileset::image() const return &mImage; } +QImage *Tileset::image() +{ + return &mImage; +} + void Tileset::setPixelColor(int x, int y, const QColor &colour) { mImage.setPixelColor(x, y, colour); diff --git a/lib/tileset.h b/lib/tileset.h index e1a271ab..7260f296 100644 --- a/lib/tileset.h +++ b/lib/tileset.h @@ -39,6 +39,7 @@ class SLATE_EXPORT Tileset : public QObject QString fileName() const; void setFileName(const QString &fileName); const QImage *image() const; + QImage *image(); void setPixelColor(int x, int y, const QColor &colour); void copy(const QPoint &sourceTopLeft, const QPoint &targetTopLeft); void rotateCounterClockwise(const QPoint &tileTopLeft); diff --git a/lib/utils.h b/lib/utils.h index 32faf499..771d6c35 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -46,6 +46,28 @@ namespace Utils { debug << enumValue; return string; } + + template + inline T divFloor(const T dividend, const T divisor) { + T quotient = dividend / divisor; + const T remainder = dividend % divisor; + if ((remainder != 0) && ((remainder < 0) != (divisor < 0))) --quotient; + return quotient; + } + template + inline T divCeil(const T dividend, const T divisor) { + return divFloor(dividend + (divisor - 1), divisor); + } + template + inline T modFloor(const T dividend, const T divisor) { + T remainder = dividend % divisor; + if ((remainder != 0) && ((remainder < 0) != (divisor < 0))) remainder += divisor; + return remainder; + } + template + inline T modCeil(const T dividend, const T divisor) { + return modFloor(dividend + (divisor - 1), divisor); + } } #endif // UTILS_H