From: Julian Smart Date: Sun, 6 Feb 2000 14:51:36 +0000 (+0000) Subject: Added rotation to wxImage X-Git-Url: https://git.saurik.com/wxWidgets.git/commitdiff_plain/7a632f1056d7894daca7342394c1db9b61f6aecf Added rotation to wxImage git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@5872 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- diff --git a/docs/latex/wx/image.tex b/docs/latex/wx/image.tex index ca43c0fbcc..f913b2b6ba 100644 --- a/docs/latex/wx/image.tex +++ b/docs/latex/wx/image.tex @@ -505,6 +505,16 @@ Returns the (modified) image itself. \helpref{Scale}{wximagescale} +\membersection{wxImage::Rotate}\label{wximagererotate} + +\func{wxImage}{Rotate}{\param{double}{ angle}, \param{const wxPoint\& }{rotationCentre}, + \param{bool}{ interpolating = TRUE}, \param{wxPoint*}{ offsetAfterRotation = NULL}} + +Rotates the image about the given point, by {\it angle} radians. Passing TRUE +to {\it interpolating} results in better image quality, but is slower. + +Returns the rotated image, leaving this image intact. + \membersection{wxImage::Scale}\label{wximagescale} \constfunc{wxImage}{Scale}{\param{int}{ width}, \param{int}{ height}} diff --git a/include/wx/image.h b/include/wx/image.h index 7f2b0904ea..09aa6ed4a2 100644 --- a/include/wx/image.h +++ b/include/wx/image.h @@ -117,6 +117,11 @@ public: // rescales the image in place wxImage& Rescale( int width, int height ) { return *this = Scale(width, height); } + // Rotates the image about the given point, 'angle' radians. + // Returns the rotated image, leaving this image intact. + wxImage Rotate(double angle, const wxPoint & centre_of_rotation, + bool interpolating = TRUE, wxPoint * offset_after_rotation = (wxPoint*) NULL) const ; + // replace one colour with another void Replace( unsigned char r1, unsigned char g1, unsigned char b1, unsigned char r2, unsigned char g2, unsigned char b2 ); diff --git a/samples/rotate/Makefile.in b/samples/rotate/Makefile.in new file mode 100644 index 0000000000..a7c8e4fcd8 --- /dev/null +++ b/samples/rotate/Makefile.in @@ -0,0 +1,21 @@ +# +# File: makefile.unx +# Author: Julian Smart +# Created: 1998 +# Updated: +# Copyright: (c) 1998 Julian Smart +# +# "%W% %G%" +# +# Makefile for rotate example (UNIX). + +top_srcdir = @top_srcdir@/.. +top_builddir = ../.. +program_dir = samples/rotate + +PROGRAM=rotate + +OBJECTS=$(PROGRAM).o + +include ../../src/makeprog.env + diff --git a/samples/rotate/kclub.bmp b/samples/rotate/kclub.bmp new file mode 100644 index 0000000000..2a4aa03a4b Binary files /dev/null and b/samples/rotate/kclub.bmp differ diff --git a/samples/rotate/makefile.b32 b/samples/rotate/makefile.b32 new file mode 100644 index 0000000000..729b634ddc --- /dev/null +++ b/samples/rotate/makefile.b32 @@ -0,0 +1,16 @@ +# +# File: makefile.b32 +# Author: Julian Smart +# Created: 1999 +# Updated: +# Copyright: +# +# Makefile : Builds sample for 32-bit BC++ + +WXDIR = $(WXWIN) + +TARGET=rotate +OBJECTS = $(TARGET).obj + +!include $(WXDIR)\src\makeprog.b32 + diff --git a/samples/rotate/makefile.g95 b/samples/rotate/makefile.g95 new file mode 100644 index 0000000000..9fdaa4bb35 --- /dev/null +++ b/samples/rotate/makefile.g95 @@ -0,0 +1,16 @@ +# +# File: makefile.g95 +# Author: Julian Smart +# Created: 1999 +# Updated: +# Copyright: (c) Julian Smart, 1999 +# +# Makefile for wxWindows sample (Cygwin/Mingw32). + +WXDIR = ../.. + +TARGET=rotate +OBJECTS = $(TARGET).o + +include $(WXDIR)/src/makeprog.g95 + diff --git a/samples/rotate/makefile.unx b/samples/rotate/makefile.unx new file mode 100644 index 0000000000..b4e1b2ef88 --- /dev/null +++ b/samples/rotate/makefile.unx @@ -0,0 +1,35 @@ +# +# File: Makefile for samples +# Author: Robert Roebling +# Created: 1999 +# Updated: +# Copyright: (c) 1998 Robert Roebling +# +# This makefile requires a Unix version of wxWindows +# to be installed on your system. This is most often +# done typing "make install" when using the complete +# sources of wxWindows or by installing the two +# RPM packages wxGTK.XXX.rpm and wxGTK-devel.XXX.rpm +# under Linux. +# + +CC = gcc + +PROGRAM = rotate + +OBJECTS = $(PROGRAM).o + +# implementation + +.SUFFIXES: .o .cpp + +.cpp.o : + $(CC) -c `wx-config --cflags` -o $@ $< + +all: $(PROGRAM) + +$(PROGRAM): $(OBJECTS) + $(CC) -o $(PROGRAM) $(OBJECTS) `wx-config --libs` + +clean: + rm -f *.o $(PROGRAM) diff --git a/samples/rotate/makefile.vc b/samples/rotate/makefile.vc new file mode 100644 index 0000000000..315ebbcbb8 --- /dev/null +++ b/samples/rotate/makefile.vc @@ -0,0 +1,18 @@ +# +# File: makefile.vc +# Author: Julian Smart +# Created: 1999 +# Updated: +# Copyright: (c) Julian Smart +# +# Makefile : Builds sample (VC++, WIN32) +# Use FINAL=1 argument to nmake to build final version with no debug info. + +# Set WXDIR for your system +WXDIR = $(WXWIN) + +PROGRAM=rotate +OBJECTS = $(PROGRAM).obj + +!include $(WXDIR)\src\makeprog.vc + diff --git a/samples/rotate/makefile.wat b/samples/rotate/makefile.wat new file mode 100644 index 0000000000..ca68b34968 --- /dev/null +++ b/samples/rotate/makefile.wat @@ -0,0 +1,15 @@ +# +# Makefile for WATCOM +# +# Created by Julian Smart, January 1999 +# +# + +WXDIR = $(%WXWIN) + +PROGRAM = rotate +OBJECTS = $(PROGRAM).obj + +!include $(WXDIR)\src\makeprog.wat + + diff --git a/samples/rotate/mondrian.ico b/samples/rotate/mondrian.ico new file mode 100644 index 0000000000..2310c5d275 Binary files /dev/null and b/samples/rotate/mondrian.ico differ diff --git a/samples/rotate/rotate.cpp b/samples/rotate/rotate.cpp new file mode 100644 index 0000000000..74b3f80551 --- /dev/null +++ b/samples/rotate/rotate.cpp @@ -0,0 +1,120 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: test.cpp +// Purpose: Image rotation test +// Author: Carlos Moreno +// Modified by: +// Created: 6/2/2000 +// RCS-ID: $Id$ +// Copyright: (c) 2000 +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +// For compilers that support precompilation, includes "wx.h". +#include "wx/wxprec.h" + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +#include "wx/image.h" + +class MyApp: public wxApp +{ + virtual bool OnInit(); +}; + + +class MyFrame: public wxFrame +{ +public: + MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); + + void OnQuit (wxCommandEvent &); + void OnMouseLeftUp (wxMouseEvent & event); + void OnMouseRightUp (wxMouseEvent & event); + +private: + DECLARE_EVENT_TABLE() +}; + +enum +{ + ID_Quit = 1 +}; + +BEGIN_EVENT_TABLE(MyFrame, wxFrame) + EVT_MENU (ID_Quit, MyFrame::OnQuit) + EVT_LEFT_UP (MyFrame::OnMouseLeftUp) + EVT_RIGHT_UP (MyFrame::OnMouseRightUp) +END_EVENT_TABLE() + +IMPLEMENT_APP(MyApp) + + +bool MyApp::OnInit() +{ + MyFrame *frame = new MyFrame ("wxWindows Skeleton", wxPoint(20,20), wxSize(600,450)); + + frame->SetBackgroundColour (wxColour (0,80,60)); + + frame->Show (TRUE); + SetTopWindow (frame); + return TRUE; +} + +MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) + : wxFrame((wxFrame *)NULL, -1, title, pos, size) +{ + wxMenu *menuFile = new wxMenu; + menuFile->Append (ID_Quit, "E&xit"); + + wxMenuBar *menuBar = new wxMenuBar; + menuBar->Append (menuFile, "&File"); + + SetMenuBar (menuBar); +} + +void MyFrame::OnQuit (wxCommandEvent &) +{ + Close (TRUE); +} + + +// Rotate with interpolation and with offset correction +void MyFrame::OnMouseLeftUp (wxMouseEvent & event) +{ + static double angle = 0.1; + const double pi = 3.14159265359; + + wxImage img ("kclub.bmp", wxBITMAP_TYPE_BMP); + + wxPoint offset; + wxImage img2 = img.Rotate(angle, wxPoint(img.GetWidth()/2, img.GetHeight()/2), TRUE, &offset); + angle += 0.05; + + wxBitmap bmp = img2.ConvertToBitmap (); + + wxClientDC dc (this); + dc.DrawBitmap (bmp, event.m_x + offset.x, event.m_y + offset.y); + + return; +} + +// without interpolation, and without offset correction +void MyFrame::OnMouseRightUp (wxMouseEvent & event) +{ + static double angle = 0.1; + const double pi = 3.14159265359; + + wxImage img ("kclub.bmp", wxBITMAP_TYPE_BMP); + + wxImage img2 = img.Rotate(angle, wxPoint(img.GetWidth()/2, img.GetHeight()/2), FALSE); + angle += 0.05; + + wxBitmap bmp = img2.ConvertToBitmap (); + + wxClientDC dc (this); + dc.DrawBitmap (bmp, event.m_x, event.m_y); + + return; +} diff --git a/samples/rotate/rotate.rc b/samples/rotate/rotate.rc new file mode 100644 index 0000000000..7655c62a4c --- /dev/null +++ b/samples/rotate/rotate.rc @@ -0,0 +1,3 @@ +mondrian ICON "mondrian.ico" +#include "wx/msw/wx.rc" + diff --git a/src/common/image.cpp b/src/common/image.cpp index 58dec0c3b6..f73c76bfae 100644 --- a/src/common/image.cpp +++ b/src/common/image.cpp @@ -30,6 +30,7 @@ // For memcpy #include +#include #ifdef __SALFORDC__ #undef FAR @@ -2682,4 +2683,198 @@ unsigned long wxImage::ComputeHistogram( wxHashTable &h ) return nentries; } +/* + * Rotation code by Carlos Moreno + */ + +struct wxRotationPixel +{ + unsigned char rgb[3]; +}; + +struct wxRotationPoint +{ + wxRotationPoint (double _x, double _y) : x(_x), y(_y) {} + wxRotationPoint (const wxPoint & p) : x(p.x), y(p.y) {} + double x, y; +}; + +static const wxRotationPixel gs_BlankPixel = {0,0,0}; +static const double gs_Epsilon = 1e-10; + +static inline int wxCint (double x) +{ + return (x > 0) ? (int) (x + 0.5) : (int) (x - 0.5); +} + + +// Auxiliary function to rotate a point (x,y) with respect to point p0 +// make it inline and use a straight return to facilitate optimization +// also, the function receives the sine and cosine of the angle to avoid +// repeating the time-consuming calls to these functions -- sin/cos can +// be computed and stored in the calling function. + +inline wxRotationPoint rotated_point (const wxRotationPoint & p, double cos_angle, double sin_angle, const wxRotationPoint & p0) +{ + return wxRotationPoint (p0.x + (p.x - p0.x) * cos_angle - (p.y - p0.y) * sin_angle, + p0.y + (p.y - p0.y) * cos_angle + (p.x - p0.x) * sin_angle); +} + +inline wxRotationPoint rotated_point (double x, double y, double cos_angle, double sin_angle, const wxRotationPoint & p0) +{ + return rotated_point (wxRotationPoint(x,y), cos_angle, sin_angle, p0); +} + +wxImage wxImage::Rotate(double angle, const wxPoint & centre_of_rotation, bool interpolating, wxPoint * offset_after_rotation) const +{ + const wxImage& img = * this; + int i; + angle = -angle; // screen coordinates are a mirror image of "real" coordinates + + // Create pointer-based array to accelerate access to wxImage's data + wxRotationPixel ** data = new wxRotationPixel * [img.GetHeight()]; + + data[0] = (wxRotationPixel *) img.GetData(); + + for (i = 1; i < img.GetHeight(); i++) + { + data[i] = data[i - 1] + img.GetWidth(); + } + + // pre-compute coefficients for rotation formula (sine and cosine of the angle) + const double cos_angle = cos(angle); + const double sin_angle = sin(angle); + + // Create new Image to store the result + // First, find rectangle that covers the rotated image; to do that, + // rotate the four corners + + const wxRotationPoint & p0 = centre_of_rotation; + + wxRotationPoint p1 = rotated_point (0, 0, cos_angle, sin_angle, p0); + wxRotationPoint p2 = rotated_point (0, img.GetHeight(), cos_angle, sin_angle, p0); + wxRotationPoint p3 = rotated_point (img.GetWidth(), 0, cos_angle, sin_angle, p0); + wxRotationPoint p4 = rotated_point (img.GetWidth(), img.GetHeight(), cos_angle, sin_angle, p0); + + int x1 = floor (min (min(p1.x, p2.x), min(p3.x, p4.x))); + int y1 = floor (min (min(p1.y, p2.y), min(p3.y, p4.y))); + + int x2 = ceil (max (max(p1.x, p2.x), max(p3.x, p4.x))); + int y2 = ceil (max (max(p1.y, p2.y), max(p3.y, p4.y))); + + wxImage rotated (x2 - x1 + 1, y2 - y1 + 1); + + if (offset_after_rotation != NULL) + { + *offset_after_rotation = wxPoint (x1, y1); + } + + + wxRotationPixel ** result_data = new wxRotationPixel * [rotated.GetHeight()]; + + result_data[0] = (wxRotationPixel *) rotated.GetData(); + + for (i = 1; i < rotated.GetHeight(); i++) + { + result_data[i] = result_data[i - 1] + rotated.GetWidth(); + } + + // Now, for each point of the rotated image, find where it came from, by + // performing an inverse rotation (a rotation of -angle) and getting the + // pixel at those coordinates + + int x; + for (x = 0; x < rotated.GetWidth(); x++) + { + for (int y = 0; y < rotated.GetHeight(); y++) + { + wxRotationPoint src = rotated_point (x + x1, y + y1, cos_angle, -sin_angle, p0); + + if (interpolating) + { + if (0 < src.x && src.x < img.GetWidth() - 1 && + 0 < src.y && src.y < img.GetHeight() - 1) + { + // interpolate using the 4 enclosing grid-points. Those + // points can be obtained using floor and ceiling of the + // exact coordinates of the point + + const int x1 = wxCint(floor(src.x)); + const int y1 = wxCint(floor(src.y)); + const int x2 = wxCint(ceil(src.x)); + const int y2 = wxCint(ceil(src.y)); + + // get four points and the distances (square of the distance, + // for efficiency reasons) for the interpolation formula + const wxRotationPixel & v1 = data[y1][x1]; + const wxRotationPixel & v2 = data[y1][x2]; + const wxRotationPixel & v3 = data[y2][x2]; + const wxRotationPixel & v4 = data[y2][x1]; + + const double d1 = (src.x - x1) * (src.x - x1) + (src.y - y1) * (src.y - y1); + const double d2 = (src.x - x2) * (src.x - x2) + (src.y - y1) * (src.y - y1); + const double d3 = (src.x - x2) * (src.x - x2) + (src.y - y2) * (src.y - y2); + const double d4 = (src.x - x1) * (src.x - x1) + (src.y - y2) * (src.y - y2); + + // Now interpolate as a weighted average of the four surrounding + // points, where the weights are the distances to each of those points + + // If the point is exactly at one point of the grid of the source + // image, then don't interpolate -- just assign the pixel + + if (d1 < gs_Epsilon) // d1,d2,d3,d4 are positive -- no need for abs() + { + result_data[y][x] = v1; + } + else if (d2 < gs_Epsilon) + { + result_data[y][x] = v2; + } + else if (d3 < gs_Epsilon) + { + result_data[y][x] = v3; + } + else if (d4 < gs_Epsilon) + { + result_data[y][x] = v4; + } + else + { + // weights for the weighted average are proportional to the inverse of the distance + const w1 = 1/d1, w2 = 1/d2, w3 = 1/d3, w4 = 1/d4; + + for (int i = 0; i < 3; i++) // repeat calculation for R, G, and B + { + result_data[y][x].rgb[i] = + static_cast ( (w1 * v1.rgb[i] + w2 * v2.rgb[i] + + w3 * v3.rgb[i] + w4 * v4.rgb[i]) / + (w1 + w2 + w3 + w4) ); + } + } + } + else + { + result_data[y][x] = gs_BlankPixel; + } + } + else + { + const int & xs = wxCint (src.x); // wxCint performs rounding to the + const int & ys = wxCint (src.y); // closest integer + + if (0 <= xs && xs < img.GetWidth() && + 0 <= ys && ys < img.GetHeight()) + { + result_data[y][x] = data[ys][xs]; + } + else + { + result_data[y][x] = gs_BlankPixel; + } + } + } + } + + return rotated; +}