Skip to content
This repository

Improved picture zooming and rotating on touch devices #1268

Merged
merged 12 commits into from over 1 year ago

2 participants

Sascha Montellese Memphiz
Sascha Montellese
Collaborator

This PR contains better implementations for zooming/pinching and rotating images in CGUIWindowSlideShow when used with touch devices (and a few cosmetics).

This new implementation supports both the existing zooming/rotating by fixed levels (used for remote controls) and zooming/rotating by an arbitrary factor/angle which makes zooming/pinching/rotating with touch gestures much more intuitive. Nothing changes for zooming/rotating by fixed levels but for zooming/rotating by an arbitrary factor every zoom/pinch/rotation must start by sending the ACTION_GESTURE_BEGIN action and end by sending the ACTION_GESTURE_END action. In between (as it was already possible before, the ACTION_GESTURE_ZOOM/ACTION_GESTURE_ROTATE can be sent containing an arbitrary floating-point zoom factor/angle to perform zoom/pinch/rotation actions.

This PR only changes both implementation for Android and for iOS (thanks @Memphiz) but might need work on other touch input implementations but it's a lot more intuitive with this than it currently is. Currently touch based zooming/pinching is useless expect when zooming into an unzoomed image. Once the image has been zoomed any zoom/pinch action will result in unexpected behaviour. Rotation was not implemented at all because currently it is only possible to rotate in steps of 90 degrees clockwise and nothing else.

This implementation does not (yet) consider the center point of the zooming/pinching action. It will simply zoom with the center being the center of the currently visible part of the image.

Memphiz
Owner

Still works really nice :) - signed-off for ios...

added some commits August 02, 2012
Sascha Montellese cosmetics in CGUIWindowSlideShow b05249a
Sascha Montellese CGUIWindowSlideShow: unload and close both pre-loaded images 224e20e
Sascha Montellese [droid] send ACTION_GESTURE_BEGIN and ACTION_GESTURE_END on multi-tou…
…ch start and end
d377358
Sascha Montellese refactor picture zooming
The new implementation supports both zooming by fixed levels (used for remote
controls) and zooming by an arbitrary factor which makes zooming/pinching
with touch gestures much more intuitive. Nothing changes for zooming by fixed
levels but for zooming by an arbitrary factor every zoom/pinch must start by
sending the ACTION_GESTURE_BEGIN action and end by sending the
ACTION_GESTURE_END action. In between (as it was already possible before, the
ACTION_GESTURE_ZOOM can be sent containing an arbitrary floating-point zoom
factor to perform zoom/pinch actions.
666e54d
Sascha Montellese refactor picture rotating
The new implementation supports both rotating by fixed angles of +90 degree
(used for remote controls) and rotating by an arbitrary angle which makes
rotating with touch gestures much more intuitive. Nothing changes for zooming
by fixed angles. For rotating by an arbitrary factor every rotation must start
by sending the ACTION_GESTURE_BEGIN action and end by sending the
ACTION_GESTURE_END action. In between the ACTION_GESTURE_ROTATE can be sent
containing an arbitrary floating-point angle (in degrees) to perform rotation
actions.
b6f611d
Sascha Montellese CGUIWindowSlideShow: allow rotating while being zoomed e7f9217
Sascha Montellese [ios] adapt ios touch events for the new pinch strategy (thanks Memphiz) 76f8c0c
Sascha Montellese CGUIWindowSlideShow: snap back to a multiple of 90 degrees if the rot…
…ation angle is within +/- 10 degrees
c25b084
Sascha Montellese CTouchInput: add two-finger rotation detection and ITouchHandler::OnR…
…otate
8dd9530
Sascha Montellese [droid] CAndroidTouch: implement OnRotate by sending ACTION_GESTURE_R…
…OTATE
2a0904c
Sascha Montellese [ios] implement rotation gesture support (thanks Memphiz) cd4ff8f
Sascha Montellese add rotateccw action and a new optional parameter value to Player.Rot…
…ate in JSON-RPC
f5cf334
Sascha Montellese Montellese merged commit e13dcf3 into from September 03, 2012
Sascha Montellese Montellese closed this September 03, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 12 unique commits by 1 author.

Aug 10, 2012
Sascha Montellese cosmetics in CGUIWindowSlideShow b05249a
Sascha Montellese CGUIWindowSlideShow: unload and close both pre-loaded images 224e20e
Sascha Montellese [droid] send ACTION_GESTURE_BEGIN and ACTION_GESTURE_END on multi-tou…
…ch start and end
d377358
Sascha Montellese refactor picture zooming
The new implementation supports both zooming by fixed levels (used for remote
controls) and zooming by an arbitrary factor which makes zooming/pinching
with touch gestures much more intuitive. Nothing changes for zooming by fixed
levels but for zooming by an arbitrary factor every zoom/pinch must start by
sending the ACTION_GESTURE_BEGIN action and end by sending the
ACTION_GESTURE_END action. In between (as it was already possible before, the
ACTION_GESTURE_ZOOM can be sent containing an arbitrary floating-point zoom
factor to perform zoom/pinch actions.
666e54d
Sascha Montellese refactor picture rotating
The new implementation supports both rotating by fixed angles of +90 degree
(used for remote controls) and rotating by an arbitrary angle which makes
rotating with touch gestures much more intuitive. Nothing changes for zooming
by fixed angles. For rotating by an arbitrary factor every rotation must start
by sending the ACTION_GESTURE_BEGIN action and end by sending the
ACTION_GESTURE_END action. In between the ACTION_GESTURE_ROTATE can be sent
containing an arbitrary floating-point angle (in degrees) to perform rotation
actions.
b6f611d
Sascha Montellese CGUIWindowSlideShow: allow rotating while being zoomed e7f9217
Sascha Montellese [ios] adapt ios touch events for the new pinch strategy (thanks Memphiz) 76f8c0c
Sascha Montellese CGUIWindowSlideShow: snap back to a multiple of 90 degrees if the rot…
…ation angle is within +/- 10 degrees
c25b084
Sascha Montellese CTouchInput: add two-finger rotation detection and ITouchHandler::OnR…
…otate
8dd9530
Sascha Montellese [droid] CAndroidTouch: implement OnRotate by sending ACTION_GESTURE_R…
…OTATE
2a0904c
Sascha Montellese [ios] implement rotation gesture support (thanks Memphiz) cd4ff8f
Sascha Montellese add rotateccw action and a new optional parameter value to Player.Rot…
…ate in JSON-RPC
f5cf334
This page is out of date. Refresh to see the latest.
21  xbmc/android/activity/AndroidTouch.cpp
@@ -99,6 +99,20 @@ bool CAndroidTouch::OnSingleTouchStart(float x, float y)
99 99
   return true;
100 100
 }
101 101
 
  102
+bool CAndroidTouch::OnMultiTouchStart(float x, float y, int32_t pointers /* = 2 */)
  103
+{
  104
+  XBMC_TouchGesture(ACTION_GESTURE_BEGIN, x, y, 0.0f, 0.0f);
  105
+
  106
+  return true;
  107
+}
  108
+
  109
+bool CAndroidTouch::OnMultiTouchEnd(float x, float y, int32_t pointers /* = 2 */)
  110
+{
  111
+  XBMC_TouchGesture(ACTION_GESTURE_END, 0.0f, 0.0f, 0.0f, 0.0f);
  112
+
  113
+  return true;
  114
+}
  115
+
102 116
 bool CAndroidTouch::OnTouchGesturePanStart(float x, float y)
103 117
 {
104 118
   XBMC_TouchGesture(ACTION_GESTURE_BEGIN, x, y, 0.0f, 0.0f);
@@ -142,6 +156,11 @@ void CAndroidTouch::OnZoomPinch(float centerX, float centerY, float zoomFactor)
142 156
   XBMC_TouchGesture(ACTION_GESTURE_ZOOM, centerX, centerY, zoomFactor, 0);
143 157
 }
144 158
 
  159
+void CAndroidTouch::OnRotate(float centerX, float centerY, float angle)
  160
+{
  161
+  XBMC_TouchGesture(ACTION_GESTURE_ROTATE, centerX, centerY, angle, 0);
  162
+}
  163
+
145 164
 void CAndroidTouch::XBMC_Touch(uint8_t type, uint8_t button, uint16_t x, uint16_t y)
146 165
 {
147 166
   XBMC_Event newEvent;
@@ -166,6 +185,6 @@ void CAndroidTouch::XBMC_TouchGesture(int32_t action, float posX, float posY, fl
166 185
     CApplicationMessenger::Get().SendAction(CAction(action, 0, posX, posY, offsetX, offsetY), WINDOW_INVALID, false);
167 186
   else if (action == ACTION_GESTURE_END)
168 187
     CApplicationMessenger::Get().SendAction(CAction(action, 0, posX, posY, offsetX, offsetY), WINDOW_INVALID, false);
169  
-  else if (action == ACTION_GESTURE_ZOOM)
  188
+  else if (action == ACTION_GESTURE_ZOOM || action == ACTION_GESTURE_ROTATE)
170 189
     CApplicationMessenger::Get().SendAction(CAction(action, 0, posX, posY, offsetX, 0), WINDOW_INVALID, false);
171 190
 }
4  xbmc/android/activity/AndroidTouch.h
@@ -34,6 +34,9 @@ class CAndroidTouch : protected ITouchHandler
34 34
 protected:
35 35
   virtual bool OnSingleTouchStart(float x, float y);
36 36
 
  37
+  virtual bool OnMultiTouchStart(float x, float y, int32_t pointers = 2);
  38
+  virtual bool OnMultiTouchEnd(float x, float y, int32_t pointers = 2);
  39
+
37 40
   virtual bool OnTouchGesturePanStart(float x, float y);
38 41
   virtual bool OnTouchGesturePan(float x, float y, float offsetX, float offsetY, float velocityX, float velocityY);
39 42
   virtual bool OnTouchGesturePanEnd(float x, float y, float offsetX, float offsetY, float velocityX, float velocityY);
@@ -41,6 +44,7 @@ class CAndroidTouch : protected ITouchHandler
41 44
   virtual void OnSingleTap(float x, float y);
42 45
   virtual void OnSingleLongPress(float x, float y);
43 46
   virtual void OnZoomPinch(float centerX, float centerY, float zoomFactor);
  47
+  virtual void OnRotate(float centerX, float centerY, float angle);
44 48
 
45 49
 private:
46 50
   void XBMC_Touch(uint8_t type, uint8_t button, uint16_t x, uint16_t y);
3  xbmc/guilib/Key.h
@@ -138,7 +138,8 @@
138 138
 #define ACTION_CALIBRATE_SWAP_ARROWS  47 // select next arrow. Can b used in: settingsScreenCalibration.xml windowid=11
139 139
 #define ACTION_CALIBRATE_RESET        48 // reset calibration to defaults. Can b used in: settingsScreenCalibration.xml windowid=11/settingsUICalibration.xml windowid=10
140 140
 #define ACTION_ANALOG_MOVE            49 // analog thumbstick move. Can b used in: slideshow.xml window id=2007/settingsScreenCalibration.xml windowid=11/settingsUICalibration.xml windowid=10
141  
-#define ACTION_ROTATE_PICTURE         50 // rotate current picture during slideshow. Can b used in slideshow.xml window id=2007
  141
+#define ACTION_ROTATE_PICTURE_CW      50 // rotate current picture clockwise during slideshow. Can be used in slideshow.xml window id=2007
  142
+#define ACTION_ROTATE_PICTURE_CCW     51 // rotate current picture counterclockwise during slideshow. Can be used in slideshow.xml window id=2007
142 143
 
143 144
 #define ACTION_SUBTITLE_DELAY_MIN     52 // Decrease subtitle/movie Delay.  Can b used in videoFullScreen.xml window id=2005
144 145
 #define ACTION_SUBTITLE_DELAY_PLUS    53 // Increase subtitle/movie Delay.  Can b used in videoFullScreen.xml window id=2005
3  xbmc/input/ButtonTranslator.cpp
@@ -98,7 +98,8 @@ static const ActionMapping actions[] =
98 98
         {"nextcalibration"   , ACTION_CALIBRATE_SWAP_ARROWS},
99 99
         {"resetcalibration"  , ACTION_CALIBRATE_RESET},
100 100
         {"analogmove"        , ACTION_ANALOG_MOVE},
101  
-        {"rotate"            , ACTION_ROTATE_PICTURE},
  101
+        {"rotate"            , ACTION_ROTATE_PICTURE_CW},
  102
+        {"rotateccw"         , ACTION_ROTATE_PICTURE_CCW},
102 103
         {"close"             , ACTION_NAV_BACK}, // backwards compatibility
103 104
         {"subtitledelayminus", ACTION_SUBTITLE_DELAY_MIN},
104 105
         {"subtitledelay"     , ACTION_SUBTITLE_DELAY},
50  xbmc/input/TouchInput.cpp
@@ -18,13 +18,20 @@
18 18
  *
19 19
  */
20 20
 
  21
+#include <math.h>
  22
+
21 23
 #include "TouchInput.h"
22 24
 #include "threads/SingleLock.h"
23 25
 #include "utils/log.h"
24 26
 
  27
+#ifndef M_PI
  28
+#define M_PI 3.1415926535897932384626433832795028842
  29
+#endif
  30
+
25 31
 CTouchInput::CTouchInput()
26 32
      : m_holdTimeout(1000),
27 33
        m_handler(NULL),
  34
+       m_fRotateAngle(0.0f),
28 35
        m_gestureState(TouchGestureUnknown),
29 36
        m_gestureStateOld(TouchGestureUnknown)
30 37
 {
@@ -127,6 +134,7 @@ bool CTouchInput::Handle(TouchEvent event, float x, float y, int64_t time, int32
127 134
           }
128 135
 
129 136
           setGestureState(TouchGestureMultiTouchStart);
  137
+          m_fRotateAngle = 0.0f;
130 138
         }
131 139
         // Otherwise we should ignore this pointer
132 140
         else
@@ -313,6 +321,7 @@ void CTouchInput::saveLastTouch()
313 321
 void CTouchInput::handleMultiTouchGesture()
314 322
 {
315 323
   handleZoomPinch();
  324
+  handleRotation();
316 325
 }
317 326
 
318 327
 void CTouchInput::handleZoomPinch()
@@ -342,6 +351,39 @@ void CTouchInput::handleZoomPinch()
342 351
   }
343 352
 }
344 353
 
  354
+void CTouchInput::handleRotation()
  355
+{
  356
+  Pointer& primaryPointer = m_pointers[0];
  357
+  Pointer& secondaryPointer = m_pointers[1];
  358
+
  359
+  CVector last = primaryPointer.last - secondaryPointer.last;
  360
+  CVector current = primaryPointer.current - secondaryPointer.current;
  361
+
  362
+  float length = last.length() * current.length();
  363
+  if (length != 0.0f)
  364
+  {
  365
+    float centerX = (primaryPointer.current.x + secondaryPointer.current.x) / 2;
  366
+    float centerY = (primaryPointer.current.y + secondaryPointer.current.y) / 2;
  367
+
  368
+    float scalar = last.scalar(current);
  369
+    float angle = acos(scalar / length) * 180.0f / M_PI;
  370
+
  371
+    // make sure the result of acos is a valid number
  372
+    if (angle == angle)
  373
+    {
  374
+      // calculate the direction of the rotation using the
  375
+      // z-component of the cross-product of last and current
  376
+      float direction = last.x * current.y - current.x * last.y;
  377
+      if (direction < 0.0f)
  378
+        m_fRotateAngle -= angle;
  379
+      else
  380
+        m_fRotateAngle += angle;
  381
+
  382
+      OnRotate(centerX, centerY, m_fRotateAngle);
  383
+    }
  384
+  }
  385
+}
  386
+
345 387
 void CTouchInput::OnTimeout()
346 388
 {
347 389
   CSingleLock lock(m_critical);
@@ -547,3 +589,11 @@ void CTouchInput::OnZoomPinch(float centerX, float centerY, float zoomFactor)
547 589
   if (m_handler)
548 590
     m_handler->OnZoomPinch(centerX, centerY, zoomFactor);
549 591
 }
  592
+
  593
+void CTouchInput::OnRotate(float centerX, float centerY, float angle)
  594
+{
  595
+  CLog::Log(LOGDEBUG, "%s", __FUNCTION__);
  596
+
  597
+  if (m_handler)
  598
+    m_handler->OnRotate(centerX, centerY, angle);
  599
+}
14  xbmc/input/TouchInput.h
@@ -219,6 +219,16 @@ class ITouchHandler
219 219
    \sa 
220 220
    */
221 221
   virtual void OnZoomPinch(float centerX, float centerY, float zoomFactor) { }
  222
+  /*!
  223
+   \brief Two simultaneous touches have been held down and moved to perform a rotating gesture
  224
+
  225
+   \param centerX       The x coordinate (with sub-pixel) of the center of the two touches
  226
+   \param centerY       The y coordinate (with sub-pixel) of the center of the two touches
  227
+   \param angle         The clockwise angle in degrees of the rotation
  228
+   \return True if the event was handled otherwise false
  229
+   \sa
  230
+   */
  231
+  virtual void OnRotate(float centerX, float centerY, float angle) { }
222 232
 };
223 233
 
224 234
 class CTouchInput : private ITouchHandler, private ITimerCallback
@@ -306,6 +316,7 @@ class CTouchInput : private ITouchHandler, private ITimerCallback
306 316
 
307 317
   void handleMultiTouchGesture();
308 318
   void handleZoomPinch();
  319
+  void handleRotation();
309 320
 
310 321
   // implementation of ITimerCallback
311 322
   virtual void OnTimeout();
@@ -335,6 +346,7 @@ class CTouchInput : private ITouchHandler, private ITimerCallback
335 346
   virtual void OnDoubleTap(float x1, float y1, float x2, float y2);
336 347
   virtual void OnDoubleLongPress(float x1, float y1, float x2, float y2);
337 348
   virtual void OnZoomPinch(float centerX, float centerY, float zoomFactor);
  349
+  virtual void OnRotate(float centerX, float centerY, float angle);
338 350
 
339 351
   CCriticalSection m_critical;
340 352
 
@@ -342,6 +354,8 @@ class CTouchInput : private ITouchHandler, private ITimerCallback
342 354
   ITouchHandler *m_handler;
343 355
   CTimer *m_holdTimer;
344 356
 
  357
+  float m_fRotateAngle;
  358
+
345 359
   class Touch : public CVector {
346 360
     public:
347 361
       Touch() { reset(); }
2  xbmc/interfaces/http-api/XBMChttp.cpp
@@ -2151,7 +2151,7 @@ int CXbmcHttp::xbmcAction(int numParas, CStdString paras[], int theAction)
2151 2151
     {
2152 2152
       CGUIWindowSlideShow *pSlideShow = (CGUIWindowSlideShow *)g_windowManager.GetWindow(WINDOW_SLIDESHOW);
2153 2153
       if (pSlideShow) {
2154  
-        pSlideShow->OnAction(CAction(ACTION_ROTATE_PICTURE));
  2154
+        pSlideShow->OnAction(CAction(ACTION_ROTATE_PICTURE_CW));
2155 2155
         return SetResponse(openTag+"OK");
2156 2156
       }
2157 2157
       else
5  xbmc/interfaces/json-rpc/PlayerOperations.cpp
@@ -441,7 +441,10 @@ JSONRPC_STATUS CPlayerOperations::Rotate(const CStdString &method, ITransportLay
441 441
   switch (GetPlayer(parameterObject["playerid"]))
442 442
   {
443 443
     case Picture:
444  
-      SendSlideshowAction(ACTION_ROTATE_PICTURE);
  444
+      if (parameterObject["value"].asString().compare("clockwise") == 0)
  445
+        SendSlideshowAction(ACTION_ROTATE_PICTURE_CW);
  446
+      else
  447
+        SendSlideshowAction(ACTION_ROTATE_PICTURE_CCW);
445 448
       return ACK;
446 449
 
447 450
     case Video:
3  xbmc/interfaces/json-rpc/ServiceDescription.h
@@ -1181,7 +1181,8 @@ namespace JSONRPC
1181 1181
       "\"transport\": \"Response\","
1182 1182
       "\"permission\": \"ControlPlayback\","
1183 1183
       "\"params\": ["
1184  
-        "{ \"name\": \"playerid\", \"$ref\": \"Player.Id\", \"required\": true }"
  1184
+        "{ \"name\": \"playerid\", \"$ref\": \"Player.Id\", \"required\": true },"
  1185
+        "{ \"name\": \"value\", \"type\": \"string\", \"enum\": [ \"clockwise\", \"counterclockwise\" ], \"default\": \"clockwise\" }"
1185 1186
       "],"
1186 1187
       "\"returns\": \"string\""
1187 1188
     "}",
1  xbmc/interfaces/json-rpc/methods.json
@@ -318,6 +318,7 @@
318 318
     "permission": "ControlPlayback",
319 319
     "params": [
320 320
       { "name": "playerid", "$ref": "Player.Id", "required": true }
  321
+      { "name": "value", "type": "string", "enum": [ "clockwise", "counterclockwise" ], "default": "clockwise" }
321 322
     ],
322 323
     "returns": "string"
323 324
   },
6  xbmc/osx/ios/XBMCController.h
@@ -29,7 +29,7 @@
29 29
 
30 30
 @class IOSEAGLView;
31 31
 
32  
-@interface XBMCController : UIViewController
  32
+@interface XBMCController : UIViewController <UIGestureRecognizerDelegate>
33 33
 {
34 34
   UIWindow *m_window;
35 35
   IOSEAGLView  *m_glView;
@@ -38,8 +38,6 @@
38 38
   /* Touch handling */
39 39
   CGSize screensize;
40 40
   CGPoint lastGesturePoint;
41  
-  CGFloat lastPinchScale;
42  
-  CGFloat currentPinchScale;  
43 41
   CGFloat screenScale;
44 42
   bool touchBeginSignaled;
45 43
   int  m_screenIdx;
@@ -50,8 +48,6 @@
50 48
 }
51 49
 @property (readonly, nonatomic, getter=isAnimating) BOOL animating;
52 50
 @property CGPoint lastGesturePoint;
53  
-@property CGFloat lastPinchScale;
54  
-@property CGFloat currentPinchScale;
55 51
 @property CGFloat screenScale;
56 52
 @property bool touchBeginSignaled;
57 53
 @property int  m_screenIdx;
72  xbmc/osx/ios/XBMCController.mm
@@ -37,6 +37,13 @@
37 37
 #include "utils/TimeUtils.h"
38 38
 #include "Util.h"
39 39
 #include "threads/Event.h"
  40
+#include <math.h>
  41
+
  42
+#ifndef M_PI
  43
+#define M_PI 3.1415926535897932384626433832795028842
  44
+#endif
  45
+#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
  46
+
40 47
 #undef BOOL
41 48
 
42 49
 #import "IOSEAGLView.h"
@@ -68,8 +75,6 @@ -(void) terminateWithSuccess;
68 75
 @implementation XBMCController
69 76
 @synthesize animating;
70 77
 @synthesize lastGesturePoint;
71  
-@synthesize lastPinchScale;
72  
-@synthesize currentPinchScale;
73 78
 @synthesize screenScale;
74 79
 @synthesize lastEvent;
75 80
 @synthesize touchBeginSignaled;
@@ -144,6 +149,15 @@ -(void)sendKey:(XBMCKey) key
144 149
   CWinEventsIOS::MessagePush(&newEvent);
145 150
 }
146 151
 //--------------------------------------------------------------
  152
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
  153
+{
  154
+  if ([gestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
  155
+    return YES;
  156
+  }
  157
+
  158
+  return NO;
  159
+}
  160
+//--------------------------------------------------------------
147 161
 - (void)createGestureRecognizers 
148 162
 {
149 163
   //2 finger single tab - right mouse
@@ -191,10 +205,18 @@ - (void)createGestureRecognizers
191 205
     initWithTarget:self action:@selector(handlePinch:)];
192 206
 
193 207
   pinch.delaysTouchesBegan = YES;
  208
+  pinch.delegate = self;
194 209
   [self.view addGestureRecognizer:pinch];
195 210
   [pinch release];
196  
-  lastPinchScale = 1.0;
197  
-  currentPinchScale = lastPinchScale;
  211
+
  212
+  //for rotate gesture
  213
+  UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc]
  214
+                                         initWithTarget:self action:@selector(handleRotate:)];
  215
+
  216
+  rotate.delaysTouchesBegan = YES;
  217
+  rotate.delegate = self;
  218
+  [self.view addGestureRecognizer:rotate];
  219
+  [rotate release];
198 220
 }
199 221
 //--------------------------------------------------------------
200 222
 - (void) activateKeyboard:(UIView *)view
@@ -214,21 +236,49 @@ -(void)handlePinch:(UIPinchGestureRecognizer*)sender
214 236
     CGPoint point = [sender locationOfTouch:0 inView:m_glView];  
215 237
     point.x *= screenScale;
216 238
     point.y *= screenScale;
217  
-    currentPinchScale += [sender scale] - lastPinchScale;
218  
-    lastPinchScale = [sender scale];  
219 239
   
220 240
     switch(sender.state)
221 241
     {
222 242
       case UIGestureRecognizerStateBegan:  
223  
-      break;
  243
+        CApplicationMessenger::Get().SendAction(CAction(ACTION_GESTURE_BEGIN, 0, (float)point.x, (float)point.y,
  244
+                                                        0, 0), WINDOW_INVALID,false);
  245
+        break;
224 246
       case UIGestureRecognizerStateChanged:
225 247
         CApplicationMessenger::Get().SendAction(CAction(ACTION_GESTURE_ZOOM, 0, (float)point.x, (float)point.y, 
226  
-          currentPinchScale, 0), WINDOW_INVALID,false);    
227  
-      break;
  248
+                                                                   [sender scale], 0), WINDOW_INVALID,false);
  249
+        break;
228 250
       case UIGestureRecognizerStateEnded:
229  
-      break;
  251
+        CApplicationMessenger::Get().SendAction(CAction(ACTION_GESTURE_END, 0, 0, 0,
  252
+                                                        0, 0), WINDOW_INVALID,false);
  253
+        break;
230 254
       default:
231  
-      break;
  255
+        break;
  256
+    }
  257
+  }
  258
+}
  259
+//--------------------------------------------------------------
  260
+-(void)handleRotate:(UIRotationGestureRecognizer*)sender
  261
+{
  262
+  if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
  263
+  {
  264
+    CGPoint point = [sender locationOfTouch:0 inView:m_glView];
  265
+    point.x *= screenScale;
  266
+    point.y *= screenScale;
  267
+
  268
+    switch(sender.state)
  269
+    {
  270
+      case UIGestureRecognizerStateBegan:
  271
+        CApplicationMessenger::Get().SendAction(CAction(ACTION_GESTURE_BEGIN, 0, (float)point.x, (float)point.y,
  272
+                                                        0, 0), WINDOW_INVALID,false);
  273
+        break;
  274
+      case UIGestureRecognizerStateChanged:
  275
+        CApplicationMessenger::Get().SendAction(CAction(ACTION_GESTURE_ROTATE, 0, (float)point.x, (float)point.y,
  276
+                                                        RADIANS_TO_DEGREES([sender rotation]), 0), WINDOW_INVALID,false);
  277
+        break;
  278
+      case UIGestureRecognizerStateEnded:
  279
+        break;
  280
+      default:
  281
+        break;
232 282
     }
233 283
   }
234 284
 }
235  xbmc/pictures/GUIWindowSlideShow.cpp
@@ -57,6 +57,8 @@ using namespace XFILE;
57 57
 #define PICTURE_VIEW_BOX_COLOR      0xffffff00 // YELLOW
58 58
 #define PICTURE_VIEW_BOX_BACKGROUND 0xff000000 // BLACK
59 59
 
  60
+#define ROTATION_SNAP_RANGE              10.0f
  61
+
60 62
 #define FPS                                 25
61 63
 
62 64
 #define BAR_IMAGE                            1
@@ -150,7 +152,6 @@ CGUIWindowSlideShow::~CGUIWindowSlideShow(void)
150 152
   delete m_slides;
151 153
 }
152 154
 
153  
-
154 155
 bool CGUIWindowSlideShow::IsPlaying() const
155 156
 {
156 157
   return m_Image[m_iCurrentPic].IsLoaded();
@@ -168,9 +169,14 @@ void CGUIWindowSlideShow::Reset()
168 169
   m_bScreensaver = false;
169 170
   m_Image[0].UnLoad();
170 171
   m_Image[0].Close();
  172
+  m_Image[1].UnLoad();
  173
+  m_Image[1].Close();
171 174
 
172  
-  m_iRotate = 0;
  175
+  m_fRotate = 0.0f;
  176
+  m_fInitialRotate = 0.0f;
173 177
   m_iZoomFactor = 1;
  178
+  m_fZoom = 1.0f;
  179
+  m_fInitialZoom = 0.0f;
174 180
   m_iCurrentSlide = 0;
175 181
   m_iNextSlide = 1;
176 182
   m_iCurrentPic = 0;
@@ -364,8 +370,8 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
364 370
     m_bLoadNextPic = false;
365 371
     // load using the background loader
366 372
     int maxWidth, maxHeight;
367  
-    GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * zoomamount[m_iZoomFactor - 1],
368  
-                    (float)g_settings.m_ResInfo[m_Resolution].iHeight * zoomamount[m_iZoomFactor - 1],
  373
+    GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom,
  374
+                   (float)g_settings.m_ResInfo[m_Resolution].iHeight * m_fZoom,
369 375
                     maxWidth, maxHeight);
370 376
     if (!m_slides->Get(m_iCurrentSlide)->IsVideo())
371 377
       m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, m_slides->Get(m_iCurrentSlide)->GetPath(), maxWidth, maxHeight);
@@ -373,14 +379,11 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
373 379
 
374 380
   // check if we should discard an already loaded next slide
375 381
   if (m_bLoadNextPic && m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iNextSlide)
376  
-  {
377 382
     m_Image[1 - m_iCurrentPic].Close();
378  
-  }
  383
+
379 384
   // if we're reloading an image (for better res on zooming we need to close any open ones as well)
380 385
   if (m_bReloadImage && m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iCurrentSlide)
381  
-  {
382 386
     m_Image[1 - m_iCurrentPic].Close();
383  
-  }
384 387
 
385 388
   if (m_bReloadImage)
386 389
   {
@@ -388,16 +391,18 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
388 391
     { // reload the image if we need to
389 392
       CLog::Log(LOGDEBUG, "Reloading the current image %s at zoom level %i", m_slides->Get(m_iCurrentSlide)->GetPath().c_str(), m_iZoomFactor);
390 393
       // first, our maximal size for this zoom level
391  
-      int maxWidth = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * zoomamount[m_iZoomFactor - 1]);
392  
-      int maxHeight = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * zoomamount[m_iZoomFactor - 1]);
  394
+      int maxWidth = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom);
  395
+      int maxHeight = (int)((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom);
393 396
 
394 397
       // the actual maximal size of the image to optimize the sizing based on the known sizing (aspect ratio)
395 398
       int width, height;
396 399
       GetCheckedSize((float)m_Image[m_iCurrentPic].GetOriginalWidth(), (float)m_Image[m_iCurrentPic].GetOriginalHeight(), width, height);
397 400
 
398 401
       // use the smaller of the two (no point zooming in more than we have to)
399  
-      if (maxWidth < width) width = maxWidth;
400  
-      if (maxHeight < height) height = maxHeight;
  402
+      if (maxWidth < width)
  403
+        width = maxWidth;
  404
+      if (maxHeight < height)
  405
+        height = maxHeight;
401 406
 
402 407
       m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, m_slides->Get(m_iCurrentSlide)->GetPath(), width, height);
403 408
     }
@@ -408,8 +413,8 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
408 413
     { // load the next image
409 414
       CLog::Log(LOGDEBUG, "Loading the next image %s", m_slides->Get(m_iNextSlide)->GetPath().c_str());
410 415
       int maxWidth, maxHeight;
411  
-      GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * zoomamount[m_iZoomFactor - 1],
412  
-                     (float)g_settings.m_ResInfo[m_Resolution].iHeight * zoomamount[m_iZoomFactor - 1],
  416
+      GetCheckedSize((float)g_settings.m_ResInfo[m_Resolution].iWidth * m_fZoom,
  417
+                     (float)g_settings.m_ResInfo[m_Resolution].iHeight * m_fZoom,
413 418
                      maxWidth, maxHeight);
414 419
       if (!m_slides->Get(m_iNextSlide)->IsVideo())
415 420
         m_pBackgroundLoader->LoadPic(1 - m_iCurrentPic, m_iNextSlide, m_slides->Get(m_iNextSlide)->GetPath(), maxWidth, maxHeight);
@@ -431,7 +436,8 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
431 436
     CApplicationMessenger::Get().PlayFile(*m_slides->Get(m_iCurrentSlide));
432 437
     m_iCurrentSlide = m_iNextSlide;
433 438
     m_iNextSlide    = GetNextSlide();
434  
-  } 
  439
+  }
  440
+
435 441
   // Check if we should be transistioning immediately
436 442
   if (m_bLoadNextPic)
437 443
   {
@@ -475,7 +481,7 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re
475 481
     m_iNextSlide    = GetNextSlide();
476 482
 
477 483
 //    m_iZoomFactor = 1;
478  
-    m_iRotate = 0;
  484
+    m_fRotate = 0.0f;
479 485
   }
480 486
 
481 487
   if (m_Image[m_iCurrentPic].IsLoaded())
@@ -499,71 +505,77 @@ void CGUIWindowSlideShow::Render()
499 505
 
500 506
 int CGUIWindowSlideShow::GetNextSlide()
501 507
 {
502  
-  if(m_slides->Size() <= 1)
  508
+  if (m_slides->Size() <= 1)
503 509
     return m_iCurrentSlide;
504  
-  if(m_bSlideShow || m_iDirection >= 0)
505  
-    return (m_iCurrentSlide + 1                   ) % m_slides->Size();
506  
-  else
507  
-    return (m_iCurrentSlide - 1 + m_slides->Size()) % m_slides->Size();
  510
+  if (m_bSlideShow || m_iDirection >= 0)
  511
+    return (m_iCurrentSlide + 1) % m_slides->Size();
  512
+
  513
+  return (m_iCurrentSlide - 1 + m_slides->Size()) % m_slides->Size();
508 514
 }
509 515
 
510 516
 EVENT_RESULT CGUIWindowSlideShow::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
511 517
 {
512 518
   if (event.m_id == ACTION_GESTURE_NOTIFY)
513 519
   {
514  
-    if( m_iZoomFactor == 1)//zoomed out - no inertial scrolling
515  
-    {
  520
+    if (m_iZoomFactor == 1) //zoomed out - no inertial scrolling
516 521
       return EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA;
517  
-    }
518  
-    else//zoomed in - with inertia 
519  
-    {
520  
-      return EVENT_RESULT_PAN_HORIZONTAL;
521  
-    }
  522
+
  523
+    return EVENT_RESULT_PAN_HORIZONTAL;
522 524
   }  
523 525
   else if (event.m_id == ACTION_GESTURE_BEGIN)
524 526
   {
525 527
     m_firstGesturePoint = point;
  528
+    m_fInitialZoom = m_fZoom;
  529
+    m_fInitialRotate = m_fRotate;
526 530
     return EVENT_RESULT_HANDLED;
527 531
   }
528 532
   else if (event.m_id == ACTION_GESTURE_PAN)
529 533
   { // on zoomlevel 1 just detect swipe left and right
530  
-    if( m_iZoomFactor == 1 )
531  
-    {   
532  
-      if( m_firstGesturePoint.x > 0 && fabs(point.x - m_firstGesturePoint.x) > 100 )
  534
+    if (m_iZoomFactor == 1)
  535
+    {
  536
+      if (m_firstGesturePoint.x > 0 && fabs(point.x - m_firstGesturePoint.x) > 100)
533 537
       {
534  
-        if( point.x < m_firstGesturePoint.x )
535  
-        {
  538
+        if (point.x < m_firstGesturePoint.x)
536 539
           OnAction(CAction(ACTION_NEXT_PICTURE));
537  
-        }
538 540
         else 
539  
-        {
540 541
           OnAction(CAction(ACTION_PREV_PICTURE));
541  
-        }
  542
+
542 543
         m_firstGesturePoint.x = 0;
543 544
       }
544 545
     }
545  
-    else//zoomed in - free move mode
  546
+    else //zoomed in - free move mode
546 547
     {
547  
-      Move(PICTURE_MOVE_AMOUNT_TOUCH/m_iZoomFactor*(m_firstGesturePoint.x-point.x),PICTURE_MOVE_AMOUNT_TOUCH/m_iZoomFactor*(m_firstGesturePoint.y-point.y));
  548
+      Move(PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.x - point.x), PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.y - point.y));
548 549
       m_firstGesturePoint = point;
549 550
     }
550 551
     return EVENT_RESULT_HANDLED;
551 552
   }
552 553
   else if (event.m_id == ACTION_GESTURE_END)
553 554
   {
  555
+    if (m_fRotate != 0.0f)
  556
+    {
  557
+      // "snap" to nearest of 0, 90, 180 and 270 if the
  558
+      // difference in angle is +/-10 degrees
  559
+      float reminder = fmodf(m_fRotate, 90.0f);
  560
+      if (reminder < ROTATION_SNAP_RANGE)
  561
+        Rotate(-reminder);
  562
+      else if (reminder > 90.0f - ROTATION_SNAP_RANGE)
  563
+        Rotate(90.0f - reminder);
  564
+    }
  565
+
  566
+    m_fInitialZoom = 0.0f;
  567
+    m_fInitialRotate = 0.0f;
554 568
     return EVENT_RESULT_HANDLED;
555 569
   }
556 570
   else if (event.m_id == ACTION_GESTURE_ZOOM)
557 571
   {
558  
-    if( event.m_offsetX > 1)
559  
-    {
560  
-      Zoom((int)event.m_offsetX);
561  
-    }
562  
-    else 
563  
-    {
564  
-      Zoom((int)(m_iZoomFactor - event.m_offsetX));
565  
-    }
566  
-    return EVENT_RESULT_HANDLED;    
  572
+    ZoomRelative(m_fInitialZoom * event.m_offsetX, true);
  573
+    return EVENT_RESULT_HANDLED;
  574
+  }
  575
+  else if (event.m_id == ACTION_GESTURE_ROTATE)
  576
+  {
  577
+    Rotate(m_fInitialRotate + event.m_offsetX - m_fRotate, true);
  578
+    return EVENT_RESULT_HANDLED;
567 579
   }
568 580
   return EVENT_RESULT_UNHANDLED;
569 581
 }
@@ -588,17 +600,21 @@ bool CGUIWindowSlideShow::OnAction(const CAction &action)
588 600
       }
589 601
     }
590 602
     break;
  603
+
591 604
   case ACTION_PREVIOUS_MENU:
592 605
   case ACTION_NAV_BACK:
593 606
   case ACTION_STOP:
594 607
     g_windowManager.PreviousWindow();
595 608
     break;
  609
+
596 610
   case ACTION_NEXT_PICTURE:
597 611
       ShowNext();
598 612
     break;
  613
+
599 614
   case ACTION_PREV_PICTURE:
600 615
       ShowPrevious();
601 616
     break;
  617
+
602 618
   case ACTION_MOVE_RIGHT:
603 619
     if (m_iZoomFactor == 1)
604 620
       ShowNext();
@@ -644,8 +660,12 @@ bool CGUIWindowSlideShow::OnAction(const CAction &action)
644 660
     Zoom(m_iZoomFactor + 1);
645 661
     break;
646 662
 
647  
-  case ACTION_ROTATE_PICTURE:
648  
-    Rotate();
  663
+  case ACTION_ROTATE_PICTURE_CW:
  664
+    Rotate(90.0f);
  665
+    break;
  666
+
  667
+  case ACTION_ROTATE_PICTURE_CCW:
  668
+    Rotate(-90.0f);
649 669
     break;
650 670
 
651 671
   case ACTION_ZOOM_LEVEL_NORMAL:
@@ -660,9 +680,11 @@ bool CGUIWindowSlideShow::OnAction(const CAction &action)
660 680
   case ACTION_ZOOM_LEVEL_9:
661 681
     Zoom((action.GetID() - ACTION_ZOOM_LEVEL_NORMAL) + 1);
662 682
     break;
  683
+
663 684
   case ACTION_ANALOG_MOVE:
664 685
     Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, -action.GetAmount(1)*PICTURE_MOVE_AMOUNT_ANALOG);
665 686
     break;
  687
+
666 688
   default:
667 689
     return CGUIWindow::OnAction(action);
668 690
   }
@@ -699,9 +721,8 @@ bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
699 721
 
700 722
       //   Reset();
701 723
       if (message.GetParam1() != WINDOW_PICTURES)
702  
-      {
703 724
         m_ImageLib.Unload();
704  
-      }
  725
+
705 726
       g_windowManager.ShowOverlay(OVERLAY_STATE_SHOWN);
706 727
       FreeResources();
707 728
     }
@@ -713,19 +734,14 @@ bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
713 734
 
714 735
       //FIXME: Use GUI resolution for now
715 736
       if (0 /*m_Resolution != g_guiSettings.m_LookAndFeelResolution && m_Resolution != INVALID && m_Resolution!=AUTORES*/)
716  
-      {
717 737
         g_graphicsContext.SetVideoResolution(m_Resolution);
718  
-      }
719 738
       else
720  
-      {
721 739
         m_Resolution = g_graphicsContext.GetVideoResolution();
722  
-      }
723 740
 
724 741
       CGUIWindow::OnMessage(message);
725 742
       if (message.GetParam1() != WINDOW_PICTURES)
726  
-      {
727 743
         m_ImageLib.Load();
728  
-      }
  744
+
729 745
       g_windowManager.ShowOverlay(OVERLAY_STATE_HIDDEN);
730 746
 
731 747
       // turn off slideshow if we only have 1 image
@@ -735,6 +751,7 @@ bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
735 751
       return true;
736 752
     }
737 753
     break;
  754
+
738 755
   case GUI_MSG_START_SLIDESHOW:
739 756
     {
740 757
       CStdString strFolder = message.GetStringParam();
@@ -755,28 +772,31 @@ bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
755 772
       RunSlideShow(strFolder, bRecursive, bRandom, bNotRandom);
756 773
     }
757 774
     break;
  775
+
758 776
     case GUI_MSG_PLAYLISTPLAYER_STOPPED:
759  
-    {
760  
-      m_bPlayingVideo = false;
761  
-      if (m_bSlideShow)
762  
-        g_windowManager.ActivateWindow(WINDOW_SLIDESHOW);
763  
-    }
764  
-    break;
  777
+      {
  778
+        m_bPlayingVideo = false;
  779
+        if (m_bSlideShow)
  780
+          g_windowManager.ActivateWindow(WINDOW_SLIDESHOW);
  781
+      }
  782
+      break;
  783
+
765 784
     case GUI_MSG_PLAYBACK_STARTED:
766  
-    {
767  
-      if(m_bSlideShow && m_bPlayingVideo)
768  
-        g_windowManager.ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
769  
-    }
770  
-    break;
  785
+      {
  786
+        if (m_bSlideShow && m_bPlayingVideo)
  787
+          g_windowManager.ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
  788
+      }
  789
+      break;
  790
+
771 791
     case GUI_MSG_PLAYBACK_STOPPED:
772  
-    {
773  
-      if (m_bSlideShow && m_bPlayingVideo)
774 792
       {
775  
-        m_bSlideShow = false;
776  
-        g_windowManager.PreviousWindow();
  793
+        if (m_bSlideShow && m_bPlayingVideo)
  794
+        {
  795
+          m_bSlideShow = false;
  796
+          g_windowManager.PreviousWindow();
  797
+        }
777 798
       }
778  
-    }
779  
-    break;
  799
+      break;
780 800
   }
781 801
   return CGUIWindow::OnMessage(message);
782 802
 }
@@ -803,32 +823,61 @@ void CGUIWindowSlideShow::RenderPause()
803 823
 
804 824
 }
805 825
 
806  
-void CGUIWindowSlideShow::Rotate()
  826
+void CGUIWindowSlideShow::Rotate(float fAngle, bool immediate /* = false */)
807 827
 {
808  
-  if (!m_Image[m_iCurrentPic].DrawNextImage() && m_iZoomFactor == 1)
809  
-  {
810  
-    m_Image[m_iCurrentPic].Rotate(++m_iRotate);
811  
-  }
  828
+  if (m_Image[m_iCurrentPic].DrawNextImage())
  829
+    return;
  830
+
  831
+  m_fRotate += fAngle;
  832
+
  833
+  m_Image[m_iCurrentPic].Rotate(fAngle, immediate);
812 834
 }
813 835
 
814 836
 void CGUIWindowSlideShow::Zoom(int iZoom)
815 837
 {
816 838
   if (iZoom > MAX_ZOOM_FACTOR || iZoom < 1)
817  
-    return ;
  839
+    return;
  840
+
  841
+  ZoomRelative(zoomamount[iZoom - 1]);
  842
+}
  843
+
  844
+void CGUIWindowSlideShow::ZoomRelative(float fZoom, bool immediate /* = false */)
  845
+{
  846
+  if (fZoom < zoomamount[0])
  847
+    fZoom = zoomamount[0];
  848
+  else if (fZoom > zoomamount[MAX_ZOOM_FACTOR - 1])
  849
+    fZoom = zoomamount[MAX_ZOOM_FACTOR - 1];
  850
+
  851
+  if (m_Image[m_iCurrentPic].DrawNextImage())
  852
+    return;
  853
+
  854
+  m_fZoom = fZoom;
  855
+
  856
+  // find the nearest zoom factor
  857
+#ifdef RELOAD_ON_ZOOM
  858
+  int iOldZoomFactor = m_iZoomFactor;
  859
+#endif
  860
+  for (unsigned int i = 1; i < MAX_ZOOM_FACTOR; i++)
  861
+  {
  862
+    if (m_fZoom > zoomamount[i])
  863
+      continue;
  864
+
  865
+    if (fabs(m_fZoom - zoomamount[i - 1]) < fabs(m_fZoom - zoomamount[i]))
  866
+      m_iZoomFactor = i;
  867
+    else
  868
+      m_iZoomFactor = i + 1;
  869
+
  870
+    break;
  871
+  }
  872
+
818 873
   // set the zoom amount and then set so that the image is reloaded at the higher (or lower)
819 874
   // resolution as necessary
820  
-  if (!m_Image[m_iCurrentPic].DrawNextImage())
821  
-  {
822  
-    m_Image[m_iCurrentPic].Zoom(iZoom);
823  
-    // check if we need to reload the image for better resolution
  875
+  m_Image[m_iCurrentPic].Zoom(m_fZoom, immediate);
  876
+
824 877
 #ifdef RELOAD_ON_ZOOM
825  
-    if (iZoom > m_iZoomFactor && !m_Image[m_iCurrentPic].FullSize())
826  
-      m_bReloadImage = true;
827  
-    if (iZoom == 1)
828  
-      m_bReloadImage = true;
  878
+  if (m_iZoomFactor == 1 || (iZoomFactor > iOldZoomFactor && !m_Image[m_iCurrentPic].FullSize()))
  879
+    m_bReloadImage = true;
829 880
 #endif
830  
-    m_iZoomFactor = iZoom;
831  
-  }
832 881
 }
833 882
 
834 883
 void CGUIWindowSlideShow::Move(float fX, float fY)
@@ -1000,8 +1049,10 @@ void CGUIWindowSlideShow::GetCheckedSize(float width, float height, int &maxWidt
1000 1049
   }
1001 1050
   maxWidth = (int)width;
1002 1051
   maxHeight = (int)height;
1003  
-  if (maxWidth > (int)g_Windowing.GetMaxTextureSize()) maxWidth = g_Windowing.GetMaxTextureSize();
1004  
-  if (maxHeight > (int)g_Windowing.GetMaxTextureSize()) maxHeight = g_Windowing.GetMaxTextureSize();
  1052
+  if (maxWidth > (int)g_Windowing.GetMaxTextureSize())
  1053
+    maxWidth = g_Windowing.GetMaxTextureSize();
  1054
+  if (maxHeight > (int)g_Windowing.GetMaxTextureSize())
  1055
+    maxHeight = g_Windowing.GetMaxTextureSize();
1005 1056
 #else
1006 1057
   maxWidth = g_Windowing.GetMaxTextureSize();
1007 1058
   maxHeight = g_Windowing.GetMaxTextureSize();
8  xbmc/pictures/GUIWindowSlideShow.h
@@ -102,8 +102,9 @@ class CGUIWindowSlideShow : public CGUIWindow
102 102
                 SortOrder order = SortOrderAscending);
103 103
   void RenderPause();
104 104
   void RenderErrorMessage();
105  
-  void Rotate();
  105
+  void Rotate(float fAngle, bool immediate = false);
106 106
   void Zoom(int iZoom);
  107
+  void ZoomRelative(float fZoom, bool immediate = false);
107 108
   void Move(float fX, float fY);
108 109
   void GetCheckedSize(float width, float height, int &maxWidth, int &maxHeight);
109 110
   int  GetNextSlide();
@@ -111,8 +112,11 @@ class CGUIWindowSlideShow : public CGUIWindow
111 112
   int m_iCurrentSlide;
112 113
   int m_iNextSlide;
113 114
   int m_iDirection;
114  
-  int m_iRotate;
  115
+  float m_fRotate;
  116
+  float m_fInitialRotate;
115 117
   int m_iZoomFactor;
  118
+  float m_fZoom;
  119
+  float m_fInitialZoom;
116 120
 
117 121
   bool m_bShuffled;
118 122
   bool m_bSlideShow;
62  xbmc/pictures/SlideShowPicture.cpp
@@ -103,18 +103,18 @@ void CSlideShowPic::SetTexture(int iSlideNumber, CBaseTexture* pTexture, DISPLAY
103 103
   m_transistionTemp.type = TRANSISTION_NONE;
104 104
   m_fTransistionAngle = 0;
105 105
   m_fTransistionZoom = 0;
106  
-  m_fAngle = 0;
  106
+  m_fAngle = 0.0f;
107 107
   if (pTexture->GetOrientation() == 7)
108 108
   { // rotate to 270 degrees
109  
-    m_fAngle = 3.0f;
  109
+    m_fAngle = 270.0f;
110 110
   }
111 111
   if (pTexture->GetOrientation() == 2)
112 112
   { // rotate to 180 degrees
113  
-      m_fAngle = 2.0f;
  113
+      m_fAngle = 180.0f;
114 114
   }
115 115
   if (pTexture->GetOrientation() == 5)
116 116
   { // rotate to 90 degrees
117  
-    m_fAngle = 1.0f;
  117
+    m_fAngle = 90.0f;
118 118
   }
119 119
   m_fZoomAmount = 1;
120 120
   m_fZoomLeft = 0;
@@ -159,7 +159,7 @@ void CSlideShowPic::SetOriginalSize(int iOriginalWidth, int iOriginalHeight, boo
159 159
 
160 160
 int CSlideShowPic::GetOriginalWidth()
161 161
 {
162  
-  int iAngle = (int)(m_fAngle + 0.4f);
  162
+  int iAngle = (int)(m_fAngle / 90.0f + 0.4f);
163 163
   if (iAngle % 2)
164 164
     return m_iOriginalHeight;
165 165
   else
@@ -168,7 +168,7 @@ int CSlideShowPic::GetOriginalWidth()
168 168
 
169 169
 int CSlideShowPic::GetOriginalHeight()
170 170
 {
171  
-  int iAngle = (int)(m_fAngle + 0.4f);
  171
+  int iAngle = (int)(m_fAngle / 90.0f + 0.4f);
172 172
   if (iAngle % 2)
173 173
     return m_iOriginalWidth;
174 174
   else
@@ -246,27 +246,30 @@ void CSlideShowPic::Process(unsigned int currentTime, CDirtyRegionList &dirtyreg
246 246
         { // correct for any introduced inaccuracies.
247 247
           int i;
248 248
           for (i = 0; i < 10; i++)
249  
-            if (fabs(m_fZoomAmount - zoomamount[i]) < 0.01*zoomamount[i])
  249
+          {
  250
+            if (fabs(m_fZoomAmount - zoomamount[i]) < 0.01 * zoomamount[i])
250 251
               break;
  252
+          }
251 253
           m_fZoomAmount = zoomamount[i];
252 254
           m_bNoEffect = (m_fZoomAmount != 1.0f); // turn effect rendering back on.
253 255
         }
254  
-        if (m_transistionTemp.type == TRANSISTION_ROTATE)
255  
-        { // round to nearest integer for accuracy purposes
256  
-          m_fAngle = floor(m_fAngle + 0.4f);
257  
-        }
  256
+        /* not really needed anymore as we support arbitrary rotations
  257
+        else if (m_transistionTemp.type == TRANSISTION_ROTATE)
  258
+        { // round to nearest of 0, 90, 180 and 270
  259
+          float reminder = fmodf(m_fAngle, 90.0f);
  260
+          if (reminder < 45.0f)
  261
+            m_fAngle -= reminder;
  262
+          else
  263
+            m_fAngle += 90.0f - reminder;
  264
+        }*/
258 265
         m_transistionTemp.type = TRANSISTION_NONE;
259 266
       }
260 267
       else
261 268
       {
262 269
         if (m_transistionTemp.type == TRANSISTION_ROTATE)
263  
-        {
264 270
           m_fAngle += m_fTransistionAngle;
265  
-        }
266 271
         if (m_transistionTemp.type == TRANSISTION_ZOOM)
267  
-        {
268 272
           m_fZoomAmount += m_fTransistionZoom;
269  
-        }
270 273
       }
271 274
     }
272 275
   }
@@ -365,8 +368,8 @@ void CSlideShowPic::Process(unsigned int currentTime, CDirtyRegionList &dirtyreg
365 368
   // Rotate the image as needed
366 369
   float x[4];
367 370
   float y[4];
368  
-  float si = (float)sin(m_fAngle * M_PI * 0.5);
369  
-  float co = (float)cos(m_fAngle * M_PI * 0.5);
  371
+  float si = (float)sin(m_fAngle / 180.0f * M_PI);
  372
+  float co = (float)cos(m_fAngle / 180.0f * M_PI);
370 373
   x[0] = -m_fWidth * co + m_fHeight * si;
371 374
   y[0] = -m_fWidth * si - m_fHeight * co;
372 375
   x[1] = m_fWidth * co + m_fHeight * si;
@@ -600,31 +603,36 @@ void CSlideShowPic::SetTransistionTime(int iType, int iTime)