Friday, April 19, 2013

Qt: Save QPainter Output in an SVG or Image File

To save what you paint in a Qt window to a file (SVG or image) is an easy and straightforward piece of code.

You create a QSvgGenerator or a QImage object.
Some initialization.
Paint into the object (treat it as an IODevice)
And  we are done.

Suppose that this is what I draw in my paint event:

void MainWindow::paintEvent(QPaintEvent *)
{
    QPainter painter;
    painter.begin(this);
    
    painter.setRenderHint(QPainter::Antialiasing);
    paint(painter);
    
    painter.end();
}

void MainWindow::paint(QPainter &painter)
{
    painter.setClipRect(QRect(0, 0, 200, 200));
    painter.setPen(Qt::NoPen);
    painter.fillRect(QRect(0, 0, 200, 200), Qt::gray);
    painter.setPen(QPen(Qt::white, 4, Qt::DashLine));
    painter.drawLine(QLine(0, 35, 200, 35));
    painter.drawLine(QLine(0, 165, 200, 165));
}


If I want to save it into an SVG file, it would be something like this

void MainWindow::saveSvg()
{
    QString path = QFileDialog::getSaveFileName(this, tr("Save as SVG"),"", tr("SVG file (*.svg)"));

    if (path.isEmpty())
        return;

    QSvgGenerator generator;
    generator.setFileName(path);
    generator.setSize(QSize(200, 200));
    generator.setViewBox(QRect(0, 0, 200, 200));
    generator.setTitle(tr("SVG Generator Example Drawing"));
    generator.setDescription(tr("An SVG drawing created by the SVG Generator "
                             "Example provided with Qt."));
    QPainter painter;
    painter.begin(&generator);
    paint(painter);
    painter.end();
}


If I want to save it into an image file, it would be something like this.

void MainWindow::savePng()
{
    QString path = QFileDialog::getSaveFileName(this, tr("Save as image"), "", tr("PNG file (*.png)"));

    if (path.isEmpty())
        return;

    QImage img(200, 200, QImage::Format_ARGB32);

    QPainter painter;
    painter.begin(&img);
    paint(painter);
    painter.end();

    img.save(path);
}

Note that if you  replace
QImage img(200, 200, QImage::Format_ARGB32);

QPainter painter;
painter.begin(&img);
paint(painter);
painter.end();
with
QImage img(this->size(), QImage::Format_ARGB32);
QPainter painter(&img);
this->render(&painter);
you'll be taking a screenshot of widget content into an image.


References:
SVG Generator Example
Capture Qt widget as an image file

Thursday, April 11, 2013

Resizable Google Chart

To resize a Google chart (pie chart, area chart, geo chart ...) is a matter of a few lines of code after refactoring the given example in the Google Charts Gallery. The keyword is: window resize.

Just keep your data in a javascript variable and put the Google Chart code in a function to be called in each onResize event. That way the chart will be redraw to fit in the new size of the chart div, and it will look like it is resizing / scaling.

Note that you may want to set a min/max width/height for the size of the chart (css on the div can do the job just fine).

Performance? Just fine. Unless you have large data and many details to draw, then it may be a headache to draw from the beginning at each resize (you can limit the chart resize frequency with javascript by a delay or timer).

Here is a complete example:

<html>
  <head>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
    window.onresize = function(){
        startDrawingChart();
    };

    window.onload = function(){
        startDrawingChart();
    };

    var data_array = [
                      ['Year', 'Sales', 'Expenses'],
                      ['2004',  1000,      400],
                      ['2005',  1170,      460],
                      ['2006',  660,       1120],
                      ['2007',  1030,      540]
                    ];
                    
    startDrawingChart = function(){
        google.load("visualization", "1", {packages:["corechart"],callback: drawChart});

        function drawChart() {
            var data = google.visualization.arrayToDataTable(data_array);

            var options = {
              title: 'Company Performance',
              hAxis: {title: 'Year',  titleTextStyle: {color: 'red'}}
            };

            var chart = new google.visualization.AreaChart(document.getElementById('chart_div'));
            chart.draw(data, options);
        }
    };
    </script>
  </head>
  <body>
    <div id="chart_div"></div>
  </body>
</html>

Tuesday, April 9, 2013

Camera Drag and Zoom with Mouse in Unity 3D

The script is easy and descriptive, it is the result of some searching and modification.

  • Press the left mouse button and drag to move, and use mouse scroll wheel to zoom in and out.
  • The zooming code can work for both orthogonal and perspective but I've been using and testing it in orthogonal mode. 
  • Modify the public values to fit in your game.

** Just drag the script to your camera and it will work! **

C# code
[CameraDragZoom.cs]
using UnityEngine;
using System.Collections;

public class CameraDragZoom : MonoBehaviour {
    
    public float dragSpeed = -10;
    public int minX = -892;
    public int maxX = 1111;
    public int minZ = -880;
    public int maxZ = 1145;
    
    public int bottomMargin = 80; // if you have some icons at the bottom (like an RPG game) this will help preventing the drag action at the bottom
    
    public float orthZoomStep = 10.0f;
    public int orthZoomMaxSize = 500;
    public int orthZoomMinSize = 300;
    
    private bool orthographicView = true;
    private Vector3 dragOrigin;
    
    // Update is called once per frame
    void Update () {
        moveCamera();
        zoomCamera();
    }
    
    void moveCamera()
    {
        if (Input.GetMouseButtonDown(0))
        {    
            dragOrigin = Input.mousePosition;
            return;
        }

        if (!Input.GetMouseButton(0)) return;
        
        if(dragOrigin.y <= bottomMargin) return;
        
        Vector3 pos = Camera.main.ScreenToViewportPoint(Input.mousePosition - dragOrigin);
        Vector3 move = new Vector3(pos.x * dragSpeed, 0, pos.y * dragSpeed);
                
        if(move.x > 0)
        {
            if(!isWithinRightBorder())
                move.x =0;
        }
        else
        {
            if(!isWithinLeftBorder())
                move.x=0;
        }
        
        if(move.z > 0)
        {
            if(!isWithinTopBorder())
                move.z=0;
        }
        else
        {
            if(!isWithinBottomBorder())
                move.z=0;
        }
            
        
        transform.Translate(move, Space.World);
    }
    
    void zoomCamera()
    {
        if(!isWithinBorders())
            return;
        
        // zoom out
        if (Input.GetAxis("Mouse ScrollWheel") <0)
        {
            if(orthographicView)
            {
                if (Camera.main.orthographicSize <=orthZoomMaxSize)
                    Camera.main.orthographicSize += orthZoomStep;
            }
            else
            {
                if (Camera.main.fieldOfView<=150)
                       Camera.main.fieldOfView +=5;
            }
        }
        // zoom in
        if (Input.GetAxis("Mouse ScrollWheel") > 0)
           {
            if(orthographicView)
            {
                if (Camera.main.orthographicSize >= orthZoomMinSize)
                     Camera.main.orthographicSize -= orthZoomStep;            
            }
            else
            {
                if (Camera.main.fieldOfView>2)
                    Camera.main.fieldOfView -=5;
            }
           }
    }
    
    bool isWithinBorders()
    {
        return ( isWithinLeftBorder() && isWithinBottomBorder() && isWithinRightBorder() && isWithinTopBorder() );
    }
    
    bool isWithinLeftBorder()
    {
        Vector3 currentTopLeftGlobal = Camera.main.ScreenToWorldPoint(new Vector3(0,0,0));
        if(currentTopLeftGlobal.x > minX)
            return true;
        else
            return false;
        
    }
    
    bool isWithinRightBorder()
    {
        Vector3 currentBottomRightGlobal = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width,0,0));
        if(currentBottomRightGlobal.x < maxX)
            return true;
        else
            return false;
    }
    
    bool isWithinTopBorder()
    {
        Vector3 currentTopLeftGlobal = Camera.main.ScreenToWorldPoint(new Vector3(0,Screen.height,0));
        if(currentTopLeftGlobal.z < maxZ)
            return true;
        else
            return false;
    }
    
    bool isWithinBottomBorder()
    {
        Vector3 currentBottomRightGlobal = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width,0,0));
        if(currentBottomRightGlobal.z > minZ)
            return true;
        else
            return false;
    }
}