Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
SANDRO FERRAZ MARTINS FADIGA authored and SANDRO FERRAZ MARTINS FADIGA committed May 15, 2019
0 parents commit 0c0b8b1
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Water Ripple - Animation of a water ripple effect over an selected image using Qt Widgets
40 changes: 40 additions & 0 deletions WaterRipple.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#-------------------------------------------------
#
# Project created by QtCreator 2019-02-04T15:41:44
#
#-------------------------------------------------

QT += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = WaterRipple
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

CONFIG += c++11

SOURCES += \
main.cpp \
widget.cpp

HEADERS += \
widget.h

FORMS += \
widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
Binary file added anim.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();

return a.exec();
}
224 changes: 224 additions & 0 deletions widget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#include "widget.h"
#include "ui_widget.h"

#include <QFileDialog>
#include <QImageReader>
#include <QRandomGenerator>
#include <QMouseEvent>

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget),
_width(0),
_height(0),
_half_width(0),
_half_height(0),
_size(0),
_last_index(0),
_current_index(0),
_map_index(0),
_ripple_map(nullptr),
_last_map(nullptr),
_texture(nullptr),
_ripple(nullptr)
{
ui->setupUi(this);

this->setWindowTitle("Water Ripple");
ui->label->setText("");

_timer = new QTimer();
_timer->setInterval(DELAY);

_clock = new QTime();

connect(_timer, &QTimer::timeout, this, &Widget::update);
}

Widget::~Widget()
{
delete ui;
_timer->stop();
delete[] _ripple_map;
delete[] _last_map;
delete _texture;
delete _ripple;
delete _timer;
delete _clock;
}

void Widget::update()
{
// randomly create drops on the screen
if(_clock->elapsed() % 100 == 0)
dropAt(QRandomGenerator::global()->bounded(_width), QRandomGenerator::global()->bounded(_height));

// draw the pixel map on the frame buffer (paint the image on the label at the gui)
ui->label->setPixmap(QPixmap::fromImage(*_ripple));

// calculates the next pixel map
processFrame();
}

void Widget::on_pushButton_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"),
"/",
tr("Image Files (*.png *.jpg *.bmp)"),
nullptr,
QFileDialog::DontUseNativeDialog);
if(fileName.isNull() || fileName.isEmpty())
return;

// reset ui events
ui->label->removeEventFilter(this);
ui->label->setMouseTracking(false);
_timer->stop();

QImageReader ir;
ir.setFileName(fileName);

delete _texture;
_texture = new QImage(ir.read());

_width = _texture->width();
_height = _texture->height();
_half_width = _width >> 1;
_half_height = _height >> 1;
// space for 2 images (old and new), +2 to cover ripple radius <= 3
_size = _width * (_height + 2) * 2;
_last_index = _width;
// +2 from above size calc +1 more to get to 2nd image
_current_index = _width * (_height + 3);

delete _ripple_map;
_ripple_map = new int[_size] ();

delete _last_map;
_last_map = new int[_size] ();

for (int i = 0; i < _size; i++)
{
_ripple_map[i] = 0;
_last_map[i] = 0;
}

delete _ripple;
_ripple = new QImage(*_texture);

ui->label->installEventFilter(this);
ui->label->setMouseTracking(true);

_timer->start();
_clock->restart();
}

void Widget::dropAt(int dx, int dy)
{
// Make certain dx and dy are integers
// Shifting left 0 is slightly faster than parseInt and math.* (or used to be)
dx <<= 0;
dy <<= 0;

// Our ripple effect area is actually a square, not a circle
for (int j = dy - RIPPLE_RAD ; j < dy + RIPPLE_RAD ; j++)
{
for (int k = dx - RIPPLE_RAD ; k < dx + RIPPLE_RAD ; k++)
{
int i = _last_index + (j * _width) + k;
if(i < _size && i >= 0)
_ripple_map[i] += 512;
}
}
}

void Widget::processFrame()
{
int i = 0;
int a = 0;
int b = 0;
int data = 0;
int old_data = 0;

// Store indexes - old and new may be misleading/confusing
// - current and next is slightly more accurate
// - previous and current may also help in thinking
i = _last_index;
_last_index = _current_index;
_current_index = i;

// Initialize the looping values - each will be incremented
i = 0;
_map_index = _last_index;

for (int y = 0; y < _height; y++)
{
for (int x = 0; x < _width; x++)
{
// Use rippleMap to set data value, mapIdx = oldIdx
// Use averaged values of pixels: above, below, left and right of current
data = (
_ripple_map[_map_index - _width] +
_ripple_map[_map_index + _width] +
_ripple_map[_map_index - 1] +
_ripple_map[_map_index + 1]
) >> 1; // right shift 1 is same as divide by 2

// Subtract 'previous' value (we are about to overwrite rippleMap[newIdx+i])
data -= _ripple_map[_current_index + i];

// Reduce value more -- for damping
// data = data - (data / 32)
data -= data >> 5;

// Set new value
_ripple_map[_current_index + i] = data;

// If data = 0 then water is flat/still,
// If data > 0 then water has a wave
data = 1024 - data;

old_data = _last_map[i];
_last_map[i] = data;

if (old_data != data) // if no change no need to alter image
{
// Recall using "<< 0" forces integer value
// Calculate pixel offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;

// Don't go outside the image (i.e. boundary check)
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;

// maps the original texture in the paint buffer
// with the pixel changes calculated above
_ripple->setPixel(x, y, _texture->pixel(a, b));
}
_map_index++;
i++;
}
}
}

bool Widget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseMove)
{
QMouseEvent *m_event = static_cast<QMouseEvent *>(event);
int x = m_event->x();
int y = m_event->y();
dropAt(x, y);
return true;
}
else
{
// standard event processing
return QObject::eventFilter(obj, event);
}
}

79 changes: 79 additions & 0 deletions widget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>
#include <QTime>

static const int RIPPLE_RAD = 3;
static const int DELAY = 30; // delay is desired animation FPS

namespace Ui {
class Widget;
}

//!
//! \brief The Widget class - this widget implements the Water Ripple effect, the idea
//! and some of the code were copied from: http://agilerepose.weebly.com/water-ripple.html
//! I kept some of the original coments on the code...
//!
class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent = nullptr);
~Widget();

// QWidget interface
protected:
bool eventFilter(QObject *obj, QEvent *event);

private slots:
void on_pushButton_clicked();
void update();

private:

// -------------------------------------------------------
// Drop something in the water at location: dx, dy
// -------------------------------------------------------
void dropAt(int dx, int dy);

// -------------------------------------------------------
// Create the next frame of the ripple effect
// -------------------------------------------------------
void processFrame();

private:
Ui::Widget *ui;

//!
//! \brief _timer - the event timer to control animation loop
//!
QTimer* _timer;

//!
//! \brief _clock - used to control the random drop event
//!
QTime* _clock;

int _width;
int _height;
int _half_width;
int _half_height;
int _size; // space for 2 images (old and new), +2 to cover ripple radius <= 3

int _last_index;
int _current_index; // +2 from above size calc +1 more to get to 2nd image
int _map_index;

int *_ripple_map;
int *_last_map;

QImage* _texture;
QImage* _ripple;

};

#endif // WIDGET_H
36 changes: 36 additions & 0 deletions widget.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Select background image...</string>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

0 comments on commit 0c0b8b1

Please sign in to comment.