I am using wxWidgets 'An Image Panel' code. Here I made only one change.
I want frame size should be equal to image size, My Image size is 762x463, but my frame size is different. frame SetSize function is not working.
wxImagePanel::wxImagePanel(wxFrame* parent, wxString file, wxBitmapType format) :
wxPanel(parent)
{
image.LoadFile(file, format);
}
void wxImagePanel::paintEvent(wxPaintEvent & evt)
{
// depending on your system you may need to look at double-buffered dcs
wxPaintDC dc(this);
render(dc);
}
void wxImagePanel::paintNow()
{
// depending on your system you may need to look at double-buffered dcs
wxClientDC dc(this);
render(dc);
}
void wxImagePanel::render(wxDC& dc)
{
dc.DrawBitmap( image, 0, 0, false );
}
class MyApp: public wxApp
{
wxFrame *frame;
wxImagePanel * drawPane;
public:
bool OnInit()
{
// make sure to call this first
wxInitAllImageHandlers();
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
wxBitmap image(wxT("properties.png"), wxBITMAP_TYPE_PNG);
frame = new wxFrame(NULL, wxID_ANY, wxT("Hello wxDC"), wxPoint(50,50), wxSize(image.GetWidth(), image.GetHeight())); // 762x463
// then simply create like this
drawPane = new wxImagePanel( frame, wxT("image.jpg"), wxBITMAP_TYPE_JPEG);
sizer->Add(drawPane, 1, wxEXPAND);
frame->SetSizer(sizer);
frame->Show();
return true;
}
};
To make the frame sized to display the image, you need to make 2 changes:
In the constructor wxImagePanel::wxImagePanel, you need to add a line to set a minimum size for the image panel.
wxImagePanel::wxImagePanel(wxFrame* parent, wxString file, wxBitmapType format) :
wxPanel(parent)
{
image.LoadFile(file, format);
SetMinSize(image.GetSize());
...
An alternate, and possibly better, solution would be to override wxWindow::DoGetBestClientSize for your wxImagePanel class and have it return image.GetSize();.
Incidentally, in the code shown above, there is nothing that actually connects the paint handler to the paint event, so you probably also want to add a line like
Bind(wxEVT_PAINT, &wxImagePanel::paintEvent, this);
to the constructor as well.
In the body of MyApp::OnInit, change the line
frame->SetSizer(sizer);
to
frame->SetSizerAndFit(sizer);
Using the SetSizerAndFit method will tell the frame to resize itself to the smallest size necessary to display all of its contents. Since in step 1 the minimum size of the image panel was set to the size of image and the image panel is the only content of the frame, the frame will be sized to fit the image.
Related
I am creating an animation timeline widget using wxWidgets.
The widget consist of a wxFrame with a wxScrolledWindow child:
I want to support horizontal zooming by changing the timeline window virtual size when the user rolls mouse middle button.
Zooming without scrolling works well. But when i start scrolling(forward and backward) things get messed up.
So to troubleshoot I decided to do a simple test:
Draw a line from the window top left corner to the bottom right corner. And the test fails!
Without scrolling -> Test pass
Zoom then scroll to right -> Test fails
Scroll back to left -> Test also fails
I have no idea of whats going on. Here my CPP code:
In .H:
class AnimationFrame : public wxFrame
{
public:
AnimationFrame();
private:
void onTimelineMouseWheel(wxMouseEvent& ev);
void onTimelinePaint(wxPaintEvent& ev);
// The scrolled window.
wxScrolledWindow* m_timelineWindow;
};
In .CPP:
AnimationFrame::AnimationFrame()
{
assert(wxXmlResource::Get()->LoadFrame(this, NULL, "AnimationFrame"));
m_timelineWindow = (wxScrolledWindow*)wxWindow::FindWindowByName("TimelineScrolledWindow", this);
assert(m_timelineWindow);
wxPanel* headerPanel = (wxPanel*)wxWindow::FindWindowByName("HeaderPanel", this);
assert(headerPanel);
// Adjust panel sizes.
{
headerPanel->SetMaxSize(wxSize(headerPanel->GetMaxSize().x, 40));
m_timelineWindow->SetMaxSize(wxSize(m_timelineWindow->GetMaxSize().x, 100));
this->SetMinSize(wxSize(this->GetSize().x, 250));
this->SetSize(wxSize(this->GetSize().x, 250));
this->SetMaxSize(wxSize(this->GetSize().x, 250));
}
// Timeline events from the scrolled window.
m_timelineWindow->Bind(wxEVT_MOUSEWHEEL, &AnimationFrame::onTimelineMouseWheel, this);
m_timelineWindow->Bind(wxEVT_PAINT, std::bind(&AnimationFrame::onTimelinePaint, this, std::placeholders::_1));
}
void AnimationFrame::onTimelineMouseWheel(wxMouseEvent& ev)
{
static const nbFloat32 scrollIntensity = 1.20f;
const nbFloat32 delta = scrollIntensity * (nbFloat32)(ev.GetWheelRotation() / ev.GetWheelDelta());
const nbFloat32 sizeFactor = delta >= 0.0 ? delta : 1.0f / -delta;
const wxSize currentVirtualSize = m_timelineWindow->GetVirtualSize();
wxSize targetVirtualSize = wxSize((nbUint32)((nbFloat32)currentVirtualSize.x * sizeFactor), currentVirtualSize.y);
targetVirtualSize.x = std::min(targetVirtualSize.x, 5000);
targetVirtualSize.x = std::max(targetVirtualSize.x, m_timelineWindow->GetSize().x);
m_timelineWindow->SetVirtualSize(targetVirtualSize);
this->Refresh();
}
void AnimationFrame::onTimelinePaint(wxPaintEvent& ev)
{
// Init the DC.
wxPaintDC dc(m_timelineWindow);
PrepareDC(dc);// from the scrolling sample. Doesnt seems to do anything.
/*
wxFont font(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
dc.SetFont(font);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(*wxBLACK);
dc.SetTextBackground(*wxWHITE);
*/
// Now draw line.
dc.DrawLine(wxPoint(0, 0), wxPoint(m_timelineWindow->GetSize().x, m_timelineWindow->GetSize().y));
}
Addional information:
As you certainly realised the widget is loaded from CPP using the XRC format. Here the generated code from wxFormBuilder for the widget.
XRC: https://1drv.ms/u/s!Ak_u4fVrMHMkrTe59Fuo4X2b-fRO?e=2Z0lw0
CPP: https://1drv.ms/u/s!Ak_u4fVrMHMkrTi_8w5UIkwFTl9t?e=BZBlLs
I am trying to make a basic GUI using wxWidgets and wxFormBuilber to display a chess gameboard and chess pieces from a sprite sheet. I would really appreciate some sample code for:
uploading an image from a file
blitting an image at a specific location
Here's a simplest example I can think of. This uses this set of pieces from wikipedia (licensed under the Creative Commons license). This example only draws the black pieces, but you should be able to figure out how to draw the white pieces with a few simple modifications.
// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
// for all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWidgets headers)
#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
void OnPaint(wxPaintEvent& event);
wxPanel* m_board;
wxBitmap m_blackRook;
wxBitmap m_blackKnight;
wxBitmap m_blackBishop;
wxBitmap m_blackQueen;
wxBitmap m_blackKing;
wxBitmap m_blackPawn;
};
MyFrame::MyFrame()
:wxFrame(NULL, wxID_ANY, "Chess", wxDefaultPosition, wxSize(1000, 1000))
{
m_board = new wxPanel(this, wxID_ANY);
m_board->Bind(wxEVT_PAINT, &MyFrame::OnPaint, this);
// Load the image.
wxImage im("c:\\640px-Chess_Pieces_Sprite.svg.png", wxBITMAP_TYPE_PNG);
// Extract the images of the pieces from the larger image.
wxBitmap b(im);
m_blackKing = b.GetSubBitmap(wxRect(5,113,100,100));
m_blackQueen = b.GetSubBitmap(wxRect(110,113,100,100));
m_blackBishop = b.GetSubBitmap(wxRect(215,113,100,100));
m_blackKnight = b.GetSubBitmap(wxRect(323,113,100,100));
m_blackRook = b.GetSubBitmap(wxRect(433,113,100,100));
m_blackPawn = b.GetSubBitmap(wxRect(535,113,100,100));
}
void MyFrame::OnPaint(wxPaintEvent&)
{
wxPaintDC dc(m_board);
dc.Clear();
// Draw thie light squares
dc.SetPen(wxColour(209,139,71));
dc.SetBrush(wxColour(209,139,71));
dc.DrawRectangle(0,0,800,800);
// Draw thie dark squares
dc.SetPen(wxColour(255,206,158));
dc.SetBrush(wxColour(255,206,158));
for ( int i = 0 ; i< 8 ; ++i )
{
for ( int j = i%2 ; j< 8 ; j+=2 )
{
dc.DrawRectangle(i*100,j*100,100,100);
}
}
// Draw thie black pieces
dc.DrawBitmap(m_blackRook, 0, 0, true);
dc.DrawBitmap(m_blackKnight, 100, 0, true);
dc.DrawBitmap(m_blackBishop, 200, 0, true);
dc.DrawBitmap(m_blackQueen, 300, 0, true);
dc.DrawBitmap(m_blackKing, 400, 0, true);
dc.DrawBitmap(m_blackBishop, 500, 0, true);
dc.DrawBitmap(m_blackKnight, 600, 0, true);
dc.DrawBitmap(m_blackRook, 700, 0, true);
for ( int i = 0 ; i < 8 ; ++i )
{
dc.DrawBitmap(m_blackPawn, 100*i, 100, true);
}
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
::wxInitAllImageHandlers();
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On windows, the application will look something like this:
The important items demonstrated here are
loading the image.
extracting subbitmaps for the individual pieces.
drawing the pieces in a paint handler for the board.
Obviously, you should change the location of the png file in line 38. If you want to take a few extra steps, you can embed the png file in your application.
I tried to keep the example as simple as possible, so there are many things that could be improved upon. For example. I used a separate bitmap for each type of piece. If you want you can use a wxImageList instead. In addition it would be better to use an wxAutoBufferedPaintDC in the paint handler.
I'm using QGraphicsView and QGraphicsScene to display an uploaded image and then show some drawing on it. I'm uploading and image like so:
void MeasuresWidget::on_openAction_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Measures Application"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
The problem i'm facing is that if i upload an image that is smaller than the one that was uploaded before, there appears to be empty space, i.e. scene maintains the size of previous image(the bigger one) and is bigger than current one. I tried maintaining original size of scene in individual variable and using setSceneRect() in each upload action:
//in constructor
originalRect = ui->graphicsView->rect();
//in upload action
scene->setSceneRect(originalRect);
but result is that size of scene always stays the same and, if it's bigger than the actual image, cuts it. I used QLabel to display an image before and i used QLabel::setScaledContents() function and it worked fine for me. So, my question is can i achieve the same behaviour with QGraphicsScene?
Update 1:
Application behaves the way i want if i create new scene every upload action. The code now looks like:
void MeasuresWidget::on_openAction_triggered()
{
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
Is this ok? Can i achieve the behaviour i want without a need to create new scene every upload action?
You just have to resize the scene when you insert your pixmap based on its size.
If you define a new class inheriting from QGraphicsScene, you can handle it easily:
class GraphicsScene: public QGraphicsScene
{
public:
GraphicsScene(QRect const& rect, QObject* parent=nullptr): QGraphicsScene(rect, parent),
background(nullptr)
{}
QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap)
{
// We already have a background. Remove it
if (background)
{
removeItem(background);
delete background;
}
background = QGraphicsScene::addPixmap(pixmap);
// Move the pixmap
background->setPos(0, 0);
// Change the scene rect based on the size of the pixmap
setSceneRect(background->boundingRect());
return background;
}
private:
QGraphicsPixmapItem* background;
};
GraphicsScene* scene = new GraphicsScene(QRect());
QGraphicsView* view = new QGraphicsView();
view->setScene(scene);
view->show();
QPixmap pix1(QSize(2000, 2000));
pix1.fill(Qt::red);
QPixmap pix2(QSize(100, 300));
pix2.fill(Qt::green);
// The scene will be 2000x2000
QTimer::singleShot(1000, [=]() { scene->addPixmap(pix1); });
// The scene will be 100x300
QTimer::singleShot(10000, [=]() { scene->addPixmap(pix2); });
I need an image to be resized to entire window and maintain the option to shrink/resize window.
As a separate task it is easily accomplished but not together.
Example code that I use is:
myWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(myWindow), "name");
gtk_window_set_default_size(GTK_WINDOW(myWindow), myWidth, myHeight);
gtk_window_set_resizable(GTK_WINDOW(myWindow), TRUE);
g_signal_connect(myWindow, "delete-event", G_CALLBACK(PrivDeleteEvent), this);
g_signal_connect(myWindow, "destroy", G_CALLBACK(PrivOnDestroy), this);
myImage = gtk_image_new();
GtkWidget* container = gtk_alignment_new(0, 0, 1, 1);
gtk_container_add(GTK_CONTAINER(myWindow), container);
gtk_container_add(GTK_CONTAINER(container), myImage);
g_signal_connect(myImage, "expose-event", G_CALLBACK(PrivOnImageExposeEvent), this);
gtk_widget_show_all(myWindow);
gtk_main();
void
PrivSetImage()
{
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data(...);
gtk_image_set_from_pixbuf(GTK_IMAGE(myImage), pixbuf);
}
After image widget resized I call some rendering stuff and then show it in the GtkImage itself. This works good, i see image and I am able to resize window. But after gtk_image_set_from_pixbuf called I no longer able to shrink window smaller than size used to create Pixbuf. As if somewhere inside there is a call to gtk_widget_set_size_request with Pixbuf dimensions which blocks window to shrink past pixbuf size.
Any suggestion ?
Is it possible to make it so that all drawing to an area "A" is translated to an area "B"?
For example drawing to the area(0,0)(100,100) and have it appear in area(200,200)(300,300).
The question is actually tagged with windows and graphics. This might have been targeted to Win32 and GDI (where I've unfortunately nearly no experience). So, the following might be seen as proof of concept:
I couldn't resist to implement the idea / concept using QWindow and QPixmap.
The concept is:
open a window fullscreen (i.e. without decoration)
make a snapshot and store it internally (in my case a )
display the internal image in window (the user cannot notice the difference)
perform a loop where pixmap is modified and re-displayed periodically (depending or not depending on user input).
And this is how I did it in Qt:
I opened a QWindow and made it fullscreen. (Maximum size may make the window full screen as well but it still will have decoration (titlebar with system menu etc.) which is unintended.)
Before painting anything, a snapshot of this window is done. That's really easy in Qt using QScreen::grabWindow(). The grabbed contents is returned as QPixmap and stored as member of my derived Window class.
The visual output just paints the stored member QPixmap.
I used a QTimer to force periodical changes of the QPixmap. To keep the sample code as short as possible, I didn't make the effort of shuffling tiles. Instead, I simply scrolled the pixmap copying a small part, moving the rest upwards, and inserting the small stripe at bottom again.
The sample code qWindowRoll.cc:
#include <QtWidgets>
class Window: public QWindow {
private:
// the Qt backing store for window
QBackingStore _qBackStore;
// background pixmap
QPixmap _qPixmap;
public:
// constructor.
Window():
QWindow(),
_qBackStore(this)
{
showFullScreen();
}
// destructor.
virtual ~Window() = default;
// disabled:
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
// do something with pixmap
void changePixmap()
{
enum { n = 4 };
if (_qPixmap.height() < n) return; // not yet initialized
const QPixmap qPixmapTmp = _qPixmap.copy(0, 0, _qPixmap.width(), n);
//_qPixmap.scroll(0, -n, 0, n, _qPixmap.width(), _qPixmap.height() - n);
{ QPainter qPainter(&_qPixmap);
qPainter.drawPixmap(
QRect(0, 0, _qPixmap.width(), _qPixmap.height() - n),
_qPixmap,
QRect(0, n, _qPixmap.width(), _qPixmap.height() - n));
qPainter.drawPixmap(0, _qPixmap.height() - n, qPixmapTmp);
}
requestUpdate();
}
protected: // overloaded events
virtual bool event(QEvent *pQEvent) override
{
if (pQEvent->type() == QEvent::UpdateRequest) {
paint();
return true;
}
return QWindow::event(pQEvent);
}
virtual void resizeEvent(QResizeEvent *pQEvent)
{
_qBackStore.resize(pQEvent->size());
paint();
}
virtual void exposeEvent(QExposeEvent*) override
{
paint();
}
// shoot screen
// inspired by http://doc.qt.io/qt-5/qtwidgets-desktop-screenshot-screenshot-cpp.html
void makeScreenShot()
{
if (QScreen *pQScr = screen()) {
_qPixmap = pQScr->grabWindow(winId());
}
}
private: // internal stuff
// paint
void paint()
{
if (!isExposed()) return;
QRect qRect(0, 0, width(), height());
if (_qPixmap.width() != width() || _qPixmap.height() != height()) {
makeScreenShot();
}
_qBackStore.beginPaint(qRect);
QPaintDevice *pQPaintDevice = _qBackStore.paintDevice();
QPainter qPainter(pQPaintDevice);
qPainter.drawPixmap(0, 0, _qPixmap);
_qBackStore.endPaint();
_qBackStore.flush(qRect);
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// setup GUI
Window win;
win.setVisible(true);
// setup timer
QTimer qTimer;
qTimer.setInterval(50); // 50 ms -> 20 Hz (round about)
QObject::connect(&qTimer, &QTimer::timeout,
&win, &Window::changePixmap);
qTimer.start();
// run application
return app.exec();
}
I compiled and tested with Qt 5.9.2 on Windows 10. And this is how it looks:
Note: On my desktop, the scrolling is smooth. I manually made 4 snapshots and composed a GIF in GIMP – hence the image appears a bit stuttering.