Code for Vector Deformation
/****************************************************************************
**
** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
**
** This file is part of Qt Jambi.
**
** ** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/
package com.trolltech.demos;
import com.trolltech.examples.QtJambiExample;
import com.trolltech.qt.core.*;
import com.trolltech.qt.gui.*;
import java.util.*;
class PainterPathElements
{
public QPainterPath path;
public QPainterPath_Element elements[];
}
class PathDeformRenderer extends ArthurFrame
{
QBasicTimer m_repaintTimer = new QBasicTimer();
QTime m_repaintTracker = new QTime();
List<PainterPathElements> m_paths = new LinkedList<PainterPathElements>();
List<QPointF> m_advances = new LinkedList<QPointF>();
QRectF m_pathBounds = new QRectF();
String m_text = "";
QPixmap m_lens_pixmap = new QPixmap();
QImage m_lens_image = new QImage();
int m_fontSize = 0;
boolean m_animated = false;
double m_intensity = 0.0;
double m_radius = 0.0;
QPointF m_pos = new QPointF();
QPointF m_offset = new QPointF();
QPointF m_direction = new QPointF();
boolean m_textDirty = true;
public PathDeformRenderer(QWidget parent)
{
super(parent);
m_radius = 100;
m_pos = new QPointF(m_radius, m_radius);
m_direction = new QPointF(1, 1);
m_fontSize = 24;
m_animated = true;
m_repaintTimer.start(25, this);
m_repaintTracker.start();
m_intensity = 1;
generateLensPixmap();
}
public void setFontSize(int fontSize) { m_fontSize = fontSize; setText(m_text); }
@Override
public QSize sizeHint() { return new QSize(600, 500); }
public boolean animated() { return m_animated; }
public int radius() { return (int)m_radius; }
public int fontSize() { return m_fontSize; }
public int intensity() { return (int)m_intensity; }
public String text() { return m_text; }
public void setText(String text)
{
m_text = text;
m_textDirty = true;
update();
}
private void makeTextPaths()
{
QFont f = new QFont("times new roman,utopia");
f.setStyleStrategy(QFont.StyleStrategy.ForceOutline);
f.setPointSize(m_fontSize);
f.setStyleHint(QFont.StyleHint.Times);
QFontMetrics fm = new QFontMetrics(f);
m_paths.clear();
m_pathBounds = new QRectF();
QPointF advance = new QPointF(0, 0);
List<QPainterPath> paths = new LinkedList<QPainterPath>();
boolean do_quick = true;
for (int i=0; i<m_text.length(); ++i) {
if (m_text.charAt(i) >= 0x4ff && m_text.charAt(i) <= 0x1e00) {
do_quick = false;
break;
}
}
if (do_quick) {
for (int i=0; i<m_text.length(); ++i) {
QPainterPath path = new QPainterPath();
path.addText(advance, f, m_text.substring(i, i + 1));
m_pathBounds = m_pathBounds.united(path.boundingRect());
paths.add(path);
advance.add(new QPointF(fm.width(m_text.substring(i, i + 1)), 0));
}
} else {
QPainterPath path = new QPainterPath();
path.addText(advance, f, m_text);
m_pathBounds = m_pathBounds.united(path.boundingRect());
paths.add(path);
}
QMatrix m = new QMatrix(1, 0, 0, 1, -m_pathBounds.x(), -m_pathBounds.y());
for (int i=0; i<paths.size(); ++i)
addPath(m.map(paths.get(i)));
m_textDirty = false;
}
private void addPath(QPainterPath path)
{
PainterPathElements p = new PainterPathElements();
p.path = path;
p.elements = new QPainterPath_Element[path.elementCount()];
for (int i=0; i<path.elementCount(); ++i) {
p.elements[i] = path.elementAt(i);
}
m_paths.add(p);
}
static QRect circle_bounds(QPointF center, double radius, double compensation)
{
return new QRect((int)Math.round(center.x() - radius - compensation),
(int)Math.round(center.y() - radius - compensation),
(int)Math.round((radius + compensation) * 2),
(int)Math.round((radius + compensation) * 2));
}
final int LENS_EXTENT = 10;
void generateLensPixmap()
{
double rad = m_radius + LENS_EXTENT;
QRect bounds = circle_bounds(new QPointF(), rad, 0);
QPainter painter = new QPainter();
if (preferImage()) {
m_lens_image = new QImage(bounds.size(), QImage.Format.Format_ARGB32_Premultiplied);
m_lens_image.fill(0);
painter.begin(m_lens_image);
} else {
m_lens_pixmap = new QPixmap(bounds.size());
m_lens_pixmap.fill(new QColor(0, 0, 0, 0));
painter.begin(m_lens_pixmap);
}
QRadialGradient gr = new QRadialGradient(rad, rad, rad, 3 * rad / 5, 3 * rad / 5);
gr.setColorAt(0.0, new QColor(255, 255, 255, 191));
gr.setColorAt(0.2, new QColor(255, 255, 127, 191));
gr.setColorAt(0.9, new QColor(150, 150, 200, 63));
gr.setColorAt(0.95, new QColor(0, 0, 0, 127));
gr.setColorAt(1, new QColor(0, 0, 0, 0));
painter.setRenderHint(QPainter.RenderHint.Antialiasing);
painter.setBrush(new QBrush(gr));
painter.setPen(QPen.NoPen);
painter.drawEllipse(0, 0, bounds.width(), bounds.height());
painter.end();
}
public void setAnimated(boolean animated)
{
m_animated = animated;
if (m_animated) {
m_repaintTimer.start(25, this);
m_repaintTracker.start();
} else {
m_repaintTimer.stop();
}
}
@Override
protected void paintEvent(QPaintEvent e)
{
if (m_textDirty)
makeTextPaths();
super.paintEvent(e);
}
@Override
protected void timerEvent(QTimerEvent e)
{
if (e.timerId() == m_repaintTimer.timerId()) {
if ((new QLineF(new QPointF(0,0), m_direction)).length() > 1)
m_direction.multiply(0.995);
double time = m_repaintTracker.restart();
QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
double dx = m_direction.x();
double dy = m_direction.y();
if (time > 0) {
dx = dx * time * 0.1;
dy = dy * time * 0.1;
}
m_pos.add(new QPointF(dx, dy));
if (m_pos.x() - m_radius < 0) {
m_direction.setX(-m_direction.x());
m_pos.setX(m_radius);
} else if (m_pos.x() + m_radius > width()) {
m_direction.setX(-m_direction.x());
m_pos.setX(width() - m_radius);
}
if (m_pos.y() - m_radius < 0) {
m_direction.setY(-m_direction.y());
m_pos.setY(m_radius);
} else if (m_pos.y() + m_radius > height()) {
m_direction.setY(-m_direction.y());
m_pos.setY(height() - m_radius);
}
QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
update(rectBefore.united(rectAfter));
QApplication.syncX();
}
}
@Override
protected void mousePressEvent(QMouseEvent e)
{
setDescriptionEnabled(false);
m_repaintTimer.stop();
m_offset = new QPointF();
if ((new QLineF(m_pos, new QPointF(e.pos()))).length() <= m_radius) {
m_offset = new QPointF(m_pos.x(), m_pos.y());
m_offset.subtract(new QPointF(e.pos()));
}
mouseMoveEvent(e);
}
@Override
protected void mouseReleaseEvent(QMouseEvent e)
{
if (e.buttons().isSet(Qt.MouseButton.NoButton) && m_animated) {
m_repaintTimer.start(25, this);
m_repaintTracker.start();
}
}
@Override
protected void mouseMoveEvent(QMouseEvent e)
{
QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
if (e.type() == QEvent.Type.MouseMove) {
QPointF epos = new QPointF(e.pos());
epos.add(m_offset);
QLineF line = new QLineF(m_pos, epos);
line.setLength(line.length() * .1);
QPointF dir = new QPointF(line.dx(), line.dy());
m_direction.add(dir);
m_direction.multiply(0.5);
}
m_pos = new QPointF(e.pos());
m_pos.add(m_offset);
QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
update(rectBefore.united(rectAfter));
}
private void deformElement(QPainterPath_Element e, QPointF offset, double pts[])
{
double flip = m_intensity;
double x = e.x() + offset.x();
double y = e.y() + offset.y();
double dx = x - m_pos.x();
double dy = y - m_pos.y();
double len = m_radius - Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
x = x + flip * dx * len / m_radius;
y = y + flip * dy * len / m_radius;
}
pts[0] = x;
pts[1] = y;
}
protected QPainterPath lensDeform(PainterPathElements source, QPointF offset)
{
QPainterPath path = new QPainterPath();
double pts[] = new double[2];
for (int i=0; i<source.elements.length;) {
QPainterPath_Element e = source.elements[i++];
if (e.isLineTo()) {
deformElement(e, offset, pts);
path.lineTo(pts[0], pts[1]);
} else if (e.isMoveTo()) {
deformElement(e, offset, pts);
path.moveTo(pts[0], pts[1]);
} else if (e.isCurveTo()) {
deformElement(e, offset, pts);
double c1x = pts[0], c1y = pts[1];
e = source.elements[i++];
deformElement(e, offset, pts);
double c2x = pts[0], c2y = pts[1];
e = source.elements[i++];
deformElement(e, offset, pts);
double ex = pts[0], ey = pts[1];
path.cubicTo(c1x, c1y, c2x, c2y, ex, ey);
}
}
return path;
}
@Override
public void paint(QPainter painter)
{
int pad_x = 5;
int pad_y = 5;
int skip_x = (int)Math.round(m_pathBounds.width() + pad_x + m_fontSize / 2);
int skip_y = (int)Math.round(m_pathBounds.height() + pad_y);
painter.setPen(QPen.NoPen);
painter.setBrush(new QBrush(QColor.black));
QRectF clip = painter.clipPath().boundingRect();
int overlap = pad_x / 2;
for (int start_y=0; start_y<height(); start_y += skip_y) {
if (start_y > clip.bottom())
break;
int start_x = -overlap;
for (;start_x < width(); start_x += skip_x) {
if (start_y + skip_y >= clip.top()
&& start_x + skip_x >= clip.left()
&& start_x <= clip.right()) {
for (int i=0; i<m_paths.size(); ++i) {
QPainterPath path = lensDeform(m_paths.get(i), new QPointF(start_x, start_y));
painter.drawPath(path);
}
}
}
overlap = skip_x - (start_x - width());
}
if (preferImage()) {
painter.drawImage((int)(m_pos.x() - m_radius - LENS_EXTENT), (int)(m_pos.y() - m_radius - LENS_EXTENT), m_lens_image);
} else {
painter.drawPixmap((int)(m_pos.x() - m_radius - LENS_EXTENT), (int)(m_pos.y() - m_radius - LENS_EXTENT), m_lens_pixmap);
}
}
public void setRadius(int radius)
{
double max = Math.max(m_radius, radius);
m_radius = radius;
generateLensPixmap();
if (!m_animated || m_radius < max)
update(circle_bounds(m_pos, max, m_fontSize));
}
public void setIntensity(int intensity)
{
m_intensity = intensity / 100.0;
if (!m_animated)
update(circle_bounds(m_pos, m_radius, m_fontSize));
}
}
@QtJambiExample(name = "Deform")
public class Deform extends QWidget
{
private PathDeformRenderer m_renderer;
public Deform(QWidget parent)
{
super(parent);
setWindowTitle("Vector deformation");
setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png"));
m_renderer = new PathDeformRenderer(this);
m_renderer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding);
QGroupBox mainGroup = new QGroupBox(this);
mainGroup.setTitle("Vector Deformation");
QGroupBox radiusGroup = new QGroupBox(mainGroup);
radiusGroup.setTitle("Lens radius");
QSlider radiusSlider = new QSlider(Qt.Orientation.Horizontal, radiusGroup);
radiusSlider.setRange(50, 150);
radiusSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);
QGroupBox deformGroup = new QGroupBox(mainGroup);
deformGroup.setTitle("Deformation");
QSlider deformSlider = new QSlider(Qt.Orientation.Horizontal, deformGroup);
deformSlider.setRange(-100, 100);
deformSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);
QGroupBox fontSizeGroup = new QGroupBox(mainGroup);
fontSizeGroup.setTitle("Font Size");
QSlider fontSizeSlider = new QSlider(Qt.Orientation.Horizontal, fontSizeGroup);
fontSizeSlider.setRange(16, 200);
fontSizeSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);
QGroupBox textGroup = new QGroupBox(mainGroup);
textGroup.setTitle("Text");
QLineEdit textInput = new QLineEdit(textGroup);
QPushButton animateButton = new QPushButton(mainGroup);
animateButton.setText("Animated");
animateButton.setCheckable(true);
QPushButton showSourceButton = new QPushButton(mainGroup);
showSourceButton.setText("Show Source");
QPushButton whatsThisButton = new QPushButton(mainGroup);
whatsThisButton.setText("What's This?");
whatsThisButton.setCheckable(true);
QHBoxLayout mainLayout = new QHBoxLayout(this);
mainLayout.addWidget(m_renderer);
mainLayout.addWidget(mainGroup);
mainGroup.setFixedWidth(180);
QVBoxLayout mainGroupLayout = new QVBoxLayout(mainGroup);
mainGroupLayout.addWidget(radiusGroup);
mainGroupLayout.addWidget(deformGroup);
mainGroupLayout.addWidget(fontSizeGroup);
mainGroupLayout.addWidget(textGroup);
mainGroupLayout.addWidget(animateButton);
mainGroupLayout.addStretch(1);
mainGroupLayout.addWidget(showSourceButton);
mainGroupLayout.addWidget(whatsThisButton);
QVBoxLayout radiusGroupLayout = new QVBoxLayout(radiusGroup);
radiusGroupLayout.addWidget(radiusSlider);
QVBoxLayout deformGroupLayout = new QVBoxLayout(deformGroup);
deformGroupLayout.addWidget(deformSlider);
QVBoxLayout fontSizeGroupLayout = new QVBoxLayout(fontSizeGroup);
fontSizeGroupLayout.addWidget(fontSizeSlider);
QVBoxLayout textGroupLayout = new QVBoxLayout(textGroup);
textGroupLayout.addWidget(textInput);
textInput.textChanged.connect(m_renderer, "setText(String)");
radiusSlider.valueChanged.connect(m_renderer, "setRadius(int)");
deformSlider.valueChanged.connect(m_renderer, "setIntensity(int)");
fontSizeSlider.valueChanged.connect(m_renderer, "setFontSize(int)");
animateButton.clicked.connect(m_renderer, "setAnimated(boolean)");
whatsThisButton.clicked.connect(m_renderer, "setDescriptionEnabled(boolean)");
showSourceButton.clicked.connect(m_renderer, "showSource()");
m_renderer.descriptionEnabledChanged.connect(whatsThisButton, "setChecked(boolean)");
animateButton.animateClick();
deformSlider.setValue(80);
radiusSlider.setValue(100);
fontSizeSlider.setValue(100);
textInput.setText("Qt Jambi");
m_renderer.loadSourceFile("classpath:com/trolltech/demos/Deform.java");
m_renderer.loadDescription("classpath:com/trolltech/demos/Deform.html");
m_renderer.setDescriptionEnabled(false);
}
public static void main(String args[])
{
QApplication.initialize(args);
Deform w = new Deform(null);
w.show();
QApplication.exec();
}
}