diff --git a/src/properties/downloadedpiecesbar.cpp b/src/properties/downloadedpiecesbar.cpp new file mode 100644 index 000000000..29ecf214a --- /dev/null +++ b/src/properties/downloadedpiecesbar.cpp @@ -0,0 +1,248 @@ +/* + * Bittorrent Client using Qt4 and libtorrent. + * Copyright (C) 2006 Christophe Dumez + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * Contact : chris@qbittorrent.org + */ + +#include "downloadedpiecesbar.h" + +//#include + +DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent): QWidget(parent) +{ + setFixedHeight(BAR_HEIGHT); + + bg_color = 0xffffff; + border_color = palette().color(QPalette::Dark).rgb(); + piece_color = 0x0000ff; + piece_color_dl = 0x00d000; + + updatePieceColors(); +} + +std::vector DownloadedPiecesBar::bitfieldToFloatVector(const libtorrent::bitfield &vecin, int reqSize) +{ + std::vector result(reqSize, 0.0); + + if (vecin.size() == 0) + return result; + + const float ratio = vecin.size() / (float)reqSize; + + // simple linear transformation algorithm + // for example: + // image.x(0) = pieces.x(0.0 >= x < 1.7) + // image.x(1) = pieces.x(1.7 >= x < 3.4) + + for (int x = 0; x < reqSize; ++x) { + + // don't use previously calculated value "ratio" here!!! + // float cannot save irrational number like 7/9, if this number will be rounded up by std::ceil + // give you x2 == pieces.size(), and index out of range: pieces[x2] + // this code is safe, so keep that in mind when you try optimize more. + // tested with size = 3000000ul + + // R - real + const float fromR = (x * vecin.size()) / (float)reqSize; + const float toR = ((x + 1) * vecin.size()) / (float)reqSize; + + // C - integer + int fromC = fromR;// std::floor not needed + int toC = std::ceil(toR); + + // position in pieces table + // libtorrent::bitfield::m_size is unsigned int(31 bits), so qlonglong is not needed + // tested with size = 3000000ul + int x2 = fromC; + + // little speed up for really big pieces table, 10K+ size + const int toCMinusOne = toC - 1; + + // value in returned vector + float value = 0; + + // case when calculated range is (15.2 >= x < 15.7) + if (x2 == toCMinusOne) { + if (vecin[x2]) { + value += toR - fromR; + } + ++x2; + } + // case when (15.2 >= x < 17.8) + else { + // subcase (15.2 >= x < 16) + if (x2 != fromR) { + if (vecin[x2]) { + value += 1.0 - (fromR - fromC); + } + ++x2; + } + + // subcase (16 >= x < 17) + for (; x2 < toCMinusOne; ++x2) { + if (vecin[x2]) { + value += 1.0; + } + } + + // subcase (17 >= x < 17.8) + if (x2 == toCMinusOne) { + if (vecin[x2]) { + value += 1.0 - (toC - toR); + } + ++x2; + } + } + + // normalization <0, 1> + value /= ratio; + + // float precision sometimes gives > 1, bacause in not possible to store irrational numbers + value = qMin(value, (float)1.0); + + result[x] = value; + } + + return result; +} + + +int DownloadedPiecesBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) +{ + int r1 = qRed(rgb1); + int g1 = qGreen(rgb1); + int b1 = qBlue(rgb1); + + int r2 = qRed(rgb2); + int g2 = qGreen(rgb2); + int b2 = qBlue(rgb2); + + float ratio_n = 1.0 - ratio; + int r = (r1 * ratio_n) + (r2 * ratio); + int g = (g1 * ratio_n) + (g2 * ratio); + int b = (b1 * ratio_n) + (b2 * ratio); + + return qRgb(r, g, b); +} + +void DownloadedPiecesBar::updateImage() +{ +// qDebug() << "updateImage"; + QImage image2(width() - 2, 1, QImage::Format_RGB888); + + if (pieces.empty()) { + image2.fill(0xffffff); + image = image2; + update(); + return; + } + + std::vector scaled_pieces = bitfieldToFloatVector(pieces, image2.width()); + std::vector scaled_pieces_dl = bitfieldToFloatVector(pieces_dl, image2.width()); + + // filling image + for (unsigned int x = 0; x < scaled_pieces.size(); ++x) + { + float pieces2_val = scaled_pieces.at(x); + float pieces2_val_dl = scaled_pieces_dl.at(x); + if (pieces2_val_dl != 0) + { + float fill_ratio = pieces2_val + pieces2_val_dl; + float ratio = pieces2_val_dl / fill_ratio; + + int mixedColor = mixTwoColors(piece_color, piece_color_dl, ratio); + mixedColor = mixTwoColors(bg_color, mixedColor, fill_ratio); + + image2.setPixel(x, 0, mixedColor); + } + else + { + image2.setPixel(x, 0, piece_colors[pieces2_val * 255]); + } + } + image = image2; +} + +void DownloadedPiecesBar::setProgress(const libtorrent::bitfield &bf, const libtorrent::bitfield &bf_dl) +{ + pieces = libtorrent::bitfield(bf); + pieces_dl = libtorrent::bitfield(bf_dl); + + updateImage(); + update(); +} + +void DownloadedPiecesBar::updatePieceColors() +{ + piece_colors = std::vector(256); + for (int i = 0; i < 256; ++i) { + float ratio = (i / 255.0); + piece_colors[i] = mixTwoColors(bg_color, piece_color, ratio); + } +} + +void DownloadedPiecesBar::clear() +{ + image = QImage(); + update(); +} + +void DownloadedPiecesBar::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + QRect imageRect(1, 1, width() - 2, height() - 2); + if (image.isNull()) + { + painter.setBrush(Qt::white); + painter.drawRect(imageRect); + } + else + { + if (image.width() != imageRect.width()) + updateImage(); + painter.drawImage(imageRect, image); + } + QPainterPath border; + border.addRect(0, 0, width() - 1, height() - 1); + + painter.setPen(border_color); + painter.drawPath(border); +} + +void DownloadedPiecesBar::setColors(int background, int border, int complete, int incomplete) +{ + bg_color = background; + border_color = border; + piece_color = complete; + piece_color_dl = incomplete; + + updatePieceColors(); + updateImage(); + update(); +} + + diff --git a/src/properties/downloadedpiecesbar.h b/src/properties/downloadedpiecesbar.h index 8bb2bbf8d..9e5324507 100644 --- a/src/properties/downloadedpiecesbar.h +++ b/src/properties/downloadedpiecesbar.h @@ -33,99 +33,55 @@ #include #include -#include -#include +#include +#include #include -#include #define BAR_HEIGHT 18 class DownloadedPiecesBar: public QWidget { - Q_OBJECT - Q_DISABLE_COPY(DownloadedPiecesBar) + Q_OBJECT + Q_DISABLE_COPY(DownloadedPiecesBar) private: - QPixmap pixmap; + QImage image; + // I used values, bacause it should be possible to change colors in runtime + + // background color + int bg_color; + // border color + int border_color; + // complete piece color + int piece_color; + // incomplete piece color + int piece_color_dl; + // buffered 256 levels gradient from bg_color to piece_color + std::vector piece_colors; + + // last used bitfields, uses to better resize redraw + // TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster + libtorrent::bitfield pieces; + libtorrent::bitfield pieces_dl; + + // scale bitfield vector to float vector + std::vector bitfieldToFloatVector(const libtorrent::bitfield &vecin, int reqSize); + // mix two colors by light model, ratio <0, 1> + int mixTwoColors(int &rgb1, int &rgb2, float ratio); + // draw new image and replace actual image + void updateImage(); public: - DownloadedPiecesBar(QWidget *parent): QWidget(parent) { - setFixedHeight(BAR_HEIGHT); - } + DownloadedPiecesBar(QWidget *parent); - void setProgress(const libtorrent::bitfield &pieces, const libtorrent::bitfield &downloading_pieces) { - if(pieces.empty()) { - // Empty bar - QPixmap pix = QPixmap(1, 1); - pix.fill(); - pixmap = pix; - } else { - const qulonglong nb_pieces = pieces.size(); - // Reduce the number of pieces before creating the pixmap - // otherwise it can crash when there are too many pieces - const uint w = width(); - if(nb_pieces > w) { - const uint ratio = floor(nb_pieces/(double)w); - libtorrent::bitfield scaled_pieces(ceil(nb_pieces/(double)ratio), false); - libtorrent::bitfield scaled_downloading(ceil(nb_pieces/(double)ratio), false); - uint scaled_index = 0; - for(qulonglong i=0; i + +PieceAvailabilityBar::PieceAvailabilityBar(QWidget *parent) : + QWidget(parent) +{ + setFixedHeight(BAR_HEIGHT); + + bg_color = 0xffffff; + border_color = palette().color(QPalette::Dark).rgb(); + piece_color = 0x0000ff; + + updatePieceColors(); +} + +std::vector PieceAvailabilityBar::intToFloatVector(const std::vector &vecin, int reqSize) +{ + std::vector result(reqSize, 0.0); + + if (vecin.size() == 0) + return result; + + const float ratio = vecin.size() / (float)reqSize; + + const int maxElement = *std::max_element(vecin.begin(), vecin.end()); + + // qMax bacause in normalization we don't want divide by 0 + // if maxElement == 0 check will be disabled please enable this line: + // const int maxElement = qMax(*std::max_element(avail.begin(), avail.end()), 1); + + if (maxElement == 0) + return result; + + // simple linear transformation algorithm + // for example: + // image.x(0) = pieces.x(0.0 >= x < 1.7) + // image.x(1) = pieces.x(1.7 >= x < 3.4) + + for (int x = 0; x < reqSize; ++x) { + + // don't use previously calculated value "ratio" here!!! + // float cannot save irrational number like 7/9, if this number will be rounded up by std::ceil + // give you x2 == pieces.size(), and index out of range: pieces[x2] + // this code is safe, so keep that in mind when you try optimize more. + // tested with size = 3000000ul + + // R - real + const float fromR = (x * vecin.size()) / (float)reqSize; + const float toR = ((x + 1) * vecin.size()) / (float)reqSize; + + // C - integer + int fromC = fromR;// std::floor not needed + int toC = std::ceil(toR); + + // position in pieces table + // libtorrent::bitfield::m_size is unsigned int(31 bits), so qlonglong is not needed + // tested with size = 3000000ul + int x2 = fromC; + + // little speed up for really big pieces table, 10K+ size + const int toCMinusOne = toC - 1; + + // value in returned vector + float value = 0; + + // case when calculated range is (15.2 >= x < 15.7) + if (x2 == toCMinusOne) { + if (vecin[x2]) { + value += (toR - fromR) * vecin[x2]; + } + ++x2; + } + // case when (15.2 >= x < 17.8) + else { + // subcase (15.2 >= x < 16) + if (x2 != fromR) { + if (vecin[x2]) { + value += (1.0 - (fromR - fromC)) * vecin[x2]; + } + ++x2; + } + + // subcase (16 >= x < 17) + for (; x2 < toCMinusOne; ++x2) { + if (vecin[x2]) { + value += vecin[x2]; + } + } + + // subcase (17 >= x < 17.8) + if (x2 == toCMinusOne) { + if (vecin[x2]) { + value += (1.0 - (toC - toR)) * vecin[x2]; + } + ++x2; + } + } + + // normalization <0, 1> + value /= ratio * maxElement; + + // float precision sometimes gives > 1, bacause in not possible to store irrational numbers + value = qMin(value, (float)1.0); + + result[x] = value; + } + + return result; +} + +int PieceAvailabilityBar::mixTwoColors(int &rgb1, int &rgb2, float ratio) +{ + int r1 = qRed(rgb1); + int g1 = qGreen(rgb1); + int b1 = qBlue(rgb1); + + int r2 = qRed(rgb2); + int g2 = qGreen(rgb2); + int b2 = qBlue(rgb2); + + float ratio_n = 1.0 - ratio; + int r = (r1 * ratio_n) + (r2 * ratio); + int g = (g1 * ratio_n) + (g2 * ratio); + int b = (b1 * ratio_n) + (b2 * ratio); + + return qRgb(r, g, b); +} + +void PieceAvailabilityBar::updateImage() +{ +// qDebug() << "updateImageAv"; + QImage image2(width() - 2, 1, QImage::Format_RGB888); + + if (pieces.empty()) { + image2.fill(0xffffff); + image = image2; + update(); + return; + } + + std::vector scaled_pieces = intToFloatVector(pieces, image2.width()); + + // filling image + for (unsigned int x = 0; x < scaled_pieces.size(); ++x) + { + float pieces2_val = scaled_pieces.at(x); + image2.setPixel(x, 0, piece_colors[pieces2_val * 255]); + } + image = image2; +} + +void PieceAvailabilityBar::setAvailability(const std::vector& avail) +{ + pieces = std::vector(avail); + + updateImage(); + update(); +} + +void PieceAvailabilityBar::updatePieceColors() +{ + piece_colors = std::vector(256); + for (int i = 0; i < 256; ++i) { + float ratio = (i / 255.0); + piece_colors[i] = mixTwoColors(bg_color, piece_color, ratio); + } +} + +void PieceAvailabilityBar::clear() +{ + image = QImage(); + update(); +} + +void PieceAvailabilityBar::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + QRect imageRect(1, 1, width() - 2, height() - 2); + if (image.isNull()) + { + painter.setBrush(Qt::white); + painter.drawRect(imageRect); + } + else + { + if (image.width() != imageRect.width()) + updateImage(); + painter.drawImage(imageRect, image); + } + QPainterPath border; + border.addRect(0, 0, width() - 1, height() - 1); + + painter.setPen(border_color); + painter.drawPath(border); +} + +void PieceAvailabilityBar::setColors(int background, int border, int available) +{ + bg_color = background; + border_color = border; + piece_color = available; + + updatePieceColors(); + updateImage(); + update(); +} diff --git a/src/properties/pieceavailabilitybar.h b/src/properties/pieceavailabilitybar.h index 278cb8ce1..585347f0a 100644 --- a/src/properties/pieceavailabilitybar.h +++ b/src/properties/pieceavailabilitybar.h @@ -33,85 +33,54 @@ #include #include -#include -#include -#include +#include #include #include #define BAR_HEIGHT 18 class PieceAvailabilityBar: public QWidget { - Q_OBJECT - Q_DISABLE_COPY(PieceAvailabilityBar) + Q_OBJECT + Q_DISABLE_COPY(PieceAvailabilityBar) private: - QPixmap pixmap; + QImage image; + + // I used values, bacause it should be possible to change colors in runtime + + // background color + int bg_color; + // border color + int border_color; + // complete piece color + int piece_color; + // buffered 256 levels gradient from bg_color to piece_color + std::vector piece_colors; + + // last used int vector, uses to better resize redraw + // TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster + std::vector pieces; + + // scale int vector to float vector + std::vector intToFloatVector(const std::vector &vecin, int reqSize); + + // mix two colors by light model, ratio <0, 1> + int mixTwoColors(int &rgb1, int &rgb2, float ratio); + // draw new image and replace actual image + void updateImage(); public: - PieceAvailabilityBar(QWidget *parent): QWidget(parent) { - setFixedHeight(BAR_HEIGHT); - } + PieceAvailabilityBar(QWidget *parent); - void setAvailability(const std::vector& avail) { - if(avail.empty()) { - // Empty bar - QPixmap pix = QPixmap(1, 1); - pix.fill(); - pixmap = pix; - } else { - // Reduce the number of pieces before creating the pixmap - // otherwise it can crash when there are too many pieces - const qulonglong nb_pieces = avail.size(); - const uint w = width(); - if(nb_pieces > w) { - const qulonglong ratio = floor(nb_pieces/(double)w); - std::vector scaled_avail; - scaled_avail.reserve(ceil(nb_pieces/(double)ratio)); - for(qulonglong i=0; i& avail); + void updatePieceColors(); + void clear(); - update(); - } - - void clear() { - pixmap = QPixmap(); - update(); - } + void setColors(int background, int border, int available); protected: - void paintEvent(QPaintEvent *) { - if(pixmap.isNull()) return; - QPainter painter(this); - painter.drawPixmap(rect(), pixmap); - } + void paintEvent(QPaintEvent *); -private: - void updatePixmap(const std::vector avail) { - const int max = *std::max_element(avail.begin(), avail.end()); - if(max == 0) { - QPixmap pix = QPixmap(1, 1); - pix.fill(); - pixmap = pix; - return; - } - QPixmap pix = QPixmap(avail.size(), 1); - //pix.fill(); - QPainter painter(&pix); - for(uint i=0; i < avail.size(); ++i) { - const uint rg = 0xff - (0xff * avail[i]/max); - painter.setPen(QColor(rg, rg, 0xff)); - painter.drawPoint(i,0); - } - pixmap = pix; - } }; #endif // PIECEAVAILABILITYBAR_H diff --git a/src/properties/properties.pri b/src/properties/properties.pri index 6468da420..29db6bb0a 100644 --- a/src/properties/properties.pri +++ b/src/properties/properties.pri @@ -18,5 +18,6 @@ HEADERS += $$PWD/propertieswidget.h \ SOURCES += $$PWD/propertieswidget.cpp \ $$PWD/peerlistwidget.cpp \ $$PWD/trackerlist.cpp \ - $$PWD/proptabbar.cpp - + $$PWD/proptabbar.cpp \ + properties/downloadedpiecesbar.cpp \ + properties/pieceavailabilitybar.cpp