I have a problem. I created an image within a AbsoluteLayout with a panGestureRecognizer, but when I use that to move the image in the app, it jumps back and forth. Very annoying. Here is the code I am using
private void AddImageToPreview(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
var image = (Image)e.NewItems[e.NewItems.Count - 1];
PanGestureRecognizer panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanUpdated;
image.GestureRecognizers.Add(panGesture);
MyLayout.Children.Add(image);
}
}
void PanUpdated(object sender, PanUpdatedEventArgs args)
{
Image image = (Image)sender;
if (args.StatusType.Equals(GestureStatus.Running))
{
x = args.TotalX;
y = args.TotalY;
image.TranslateTo(x, y, 1);
}
}
Can someone help me improve this, so this drag/drop system works smooth and fast!
I have a control purely made in Xamarin.Forms that you can use for the above mentioned usage:
using System;
using Xamarin.Forms;
using FFImageLoading.Forms;
public class ZoomImage : CachedImage
{
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale, LastScale;
private double StartX, StartY;
public ZoomImage()
{
var pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinch);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
break;
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
LastScale = e.Scale;
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
if (e.Scale < 0 || Math.Abs(LastScale - e.Scale) > (LastScale * 1.3) - LastScale)
{ return; }
LastScale = e.Scale;
var current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
private T Clamp<T>(T value, T minimum, T maximum) where T: IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
What this does:
Pinch zoom, Pan and Swipe movements together with double tap centre zoom and un-zoom
Note: I have used FFimageLoading's CachedImage because I needed to cache the data in case you do not intend this replace CachedImage with Xamarin.Forms.Image
Related
Problem - I'm writing a program that draws graphics, and zooming is one of the features. Currently, a picturebox is placed on a panel, and the picturebox has vertical and horizontal scroll bars on the right and bottom. How to combine scrollbar with mouse wheel zooming? And I'm not sure if I should use paint to draw the graphics or set a bitmap to draw the graphics onto it?
Expected - When the mouse wheel is scrolled, the entire canvas(picturebox) include drawn graphics are scaled according to the current mouse position as the center (the horizontal and vertical scroll bars change according to the zoom center). When the mouse wheel is pressed and moved, the canvas can be dragged freely.
Expected as follows:
The initial code
private List<Point> _points;
private int _pointRadius = 50;
private float _scale = 1f;
private float _offsetX = 0f;
private float _offsetY = 0f;
private void picturebox_MouseDown(object sender, MouseEventArgs e)
{
_points.Add(e.Location);
}
private void picturebox_MouseWheel(object sender, MouseEvnetArgs e)
{
if(e.Delta < 0)
{
_scale += 0.1f;
_offsetX = e.X * (1f - _scale);
_offsetY = e.X * (1f - _scale);
}
else
{
_scale -= 0.1f;
_offsetX = e.X * (1f - _scale);
_offsetY = e.X * (1f - _scale);
}
picturebox.Invalidate();
}
private void picturebox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TranslateTransform(_offsetX, _offsetY);
e.Graphics.ScaleTransform(_scaleX, _scaleY);
foreach (Point p in _points)
{
e.Graphics.FillEllipse(Brushes.Black, p.X, - _pointRadius, p.Y - _pointRadius, 2 * _pointRadius, 2 * _pointRadius);
}
}
Hope the answer is modified based on the initial code.
Thanks in advance to everyone who helped me.
Would it be easier if I drew the graphics on a Bitmap?
Considering the nature of your task and the already implemented solutions in my ImageViewer I created a solution that draws the result in a Metafile, which is both elegant, consumes minimal memory and allows zooming without quality issues.
Here is the stripped version of my ImageViewer:
public class MetafileViewer : Control
{
private HScrollBar sbHorizontal = new HScrollBar { Visible = false };
private VScrollBar sbVertical = new VScrollBar { Visible = false };
private Metafile? image;
private Size imageSize;
private Rectangle targetRectangle;
private Rectangle clientRectangle;
private float zoom = 1;
private bool sbHorizontalVisible;
private bool sbVerticalVisible;
private int scrollFractionVertical;
public MetafileViewer()
{
Controls.AddRange(new Control[] { sbHorizontal, sbVertical });
sbHorizontal.ValueChanged += ScrollbarValueChanged;
sbVertical.ValueChanged += ScrollbarValueChanged;
}
void ScrollbarValueChanged(object? sender, EventArgs e) => Invalidate();
public Metafile? Image
{
get => image;
set
{
image = value;
imageSize = image?.Size ?? default;
InvalidateLayout();
}
}
public bool TryTranslate(Point mouseCoord, out PointF canvasCoord)
{
canvasCoord = default;
if (!targetRectangle.Contains(mouseCoord))
return false;
canvasCoord = new PointF((mouseCoord.X - targetRectangle.X) / zoom, (mouseCoord.Y - targetRectangle.Y) / zoom);
if (sbHorizontalVisible)
canvasCoord.X += sbHorizontal.Value / zoom;
if (sbVerticalVisible)
canvasCoord.Y += sbVertical.Value / zoom;
return true;
}
private void InvalidateLayout()
{
Invalidate();
if (imageSize.IsEmpty)
{
sbHorizontal.Visible = sbVertical.Visible = sbHorizontalVisible = sbVerticalVisible = false;
targetRectangle = Rectangle.Empty;
return;
}
Size clientSize = ClientSize;
if (clientSize.Width < 1 || clientSize.Height < 1)
{
targetRectangle = Rectangle.Empty;
return;
}
Size scaledSize = imageSize.Scale(zoom);
// scrollbars visibility
sbHorizontalVisible = scaledSize.Width > clientSize.Width
|| scaledSize.Width > clientSize.Width - SystemInformation.VerticalScrollBarWidth && scaledSize.Height > clientSize.Height;
sbVerticalVisible = scaledSize.Height > clientSize.Height
|| scaledSize.Height > clientSize.Height - SystemInformation.HorizontalScrollBarHeight && scaledSize.Width > clientSize.Width;
if (sbHorizontalVisible)
clientSize.Height -= SystemInformation.HorizontalScrollBarHeight;
if (sbVerticalVisible)
clientSize.Width -= SystemInformation.VerticalScrollBarWidth;
if (clientSize.Width < 1 || clientSize.Height < 1)
{
targetRectangle = Rectangle.Empty;
return;
}
Point clientLocation = Point.Empty;
var targetLocation = new Point((clientSize.Width >> 1) - (scaledSize.Width >> 1),
(clientSize.Height >> 1) - (scaledSize.Height >> 1));
// both scrollbars
if (sbHorizontalVisible && sbVerticalVisible)
{
sbHorizontal.Dock = sbVertical.Dock = DockStyle.None;
sbHorizontal.Width = clientSize.Width;
sbHorizontal.Top = clientSize.Height;
sbHorizontal.Left = 0;
sbVertical.Height = clientSize.Height;
sbVertical.Left = clientSize.Width;
}
// horizontal scrollbar
else if (sbHorizontalVisible)
sbHorizontal.Dock = DockStyle.Bottom;
// vertical scrollbar
else if (sbVerticalVisible)
sbVertical.Dock = DockStyle.Right;
// adjust scrollbar values
if (sbHorizontalVisible)
{
sbHorizontal.Minimum = targetLocation.X;
sbHorizontal.Maximum = targetLocation.X + scaledSize.Width;
sbHorizontal.LargeChange = clientSize.Width;
sbHorizontal.SmallChange = 32;
sbHorizontal.Value = Math.Min(sbHorizontal.Value, sbHorizontal.Maximum - sbHorizontal.LargeChange);
}
if (sbVerticalVisible)
{
sbVertical.Minimum = targetLocation.Y;
sbVertical.Maximum = targetLocation.Y + scaledSize.Height;
sbVertical.LargeChange = clientSize.Height;
sbVertical.SmallChange = 32;
sbVertical.Value = Math.Min(sbVertical.Value, sbVertical.Maximum - sbVertical.LargeChange);
}
sbHorizontal.Visible = sbHorizontalVisible;
sbVertical.Visible = sbVerticalVisible;
clientRectangle = new Rectangle(clientLocation, clientSize);
targetRectangle = new Rectangle(targetLocation, scaledSize);
if (sbVerticalVisible)
clientRectangle.X = SystemInformation.VerticalScrollBarWidth;
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InvalidateLayout();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (image == null || e.ClipRectangle.Width <= 0 || e.ClipRectangle.Height <= 0)
return;
if (targetRectangle.IsEmpty)
InvalidateLayout();
if (targetRectangle.IsEmpty)
return;
Graphics g = e.Graphics;
g.IntersectClip(clientRectangle);
Rectangle dest = targetRectangle;
if (sbHorizontalVisible)
dest.X -= sbHorizontal.Value;
if (sbVerticalVisible)
dest.Y -= sbVertical.Value;
g.DrawImage(image, dest);
g.DrawRectangle(SystemPens.ControlText, Rectangle.Inflate(targetRectangle, 1, 1));
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
switch (ModifierKeys)
{
// zoom
case Keys.Control:
float delta = (float)e.Delta / SystemInformation.MouseWheelScrollDelta / 5;
if (delta.Equals(0f))
return;
delta += 1;
SetZoom(zoom * delta);
break;
// vertical scroll
case Keys.None:
VerticalScroll(e.Delta);
break;
}
}
private void VerticalScroll(int delta)
{
// When scrolling by mouse, delta is always +-120 so this will be a small change on the scrollbar.
// But we collect the fractional changes caused by the touchpad scrolling so it will not be lost either.
int totalDelta = scrollFractionVertical + delta * sbVertical.SmallChange;
scrollFractionVertical = totalDelta % SystemInformation.MouseWheelScrollDelta;
int newValue = sbVertical.Value - totalDelta / SystemInformation.MouseWheelScrollDelta;
SetValueSafe(sbVertical, newValue);
}
internal static void SetValueSafe(ScrollBar scrollBar, int value)
{
if (value < scrollBar.Minimum)
value = scrollBar.Minimum;
else if (value > scrollBar.Maximum - scrollBar.LargeChange + 1)
value = scrollBar.Maximum - scrollBar.LargeChange + 1;
scrollBar.Value = value;
}
private void SetZoom(float value)
{
const float maxZoom = 10f;
float minZoom = image == null ? 1f : 1f / Math.Min(imageSize.Width, imageSize.Height);
if (value < minZoom)
value = minZoom;
if (value > maxZoom)
value = maxZoom;
if (zoom.Equals(value))
return;
zoom = value;
InvalidateLayout();
}
}
And then the updated version of your initial code (add a new point by right click, zoom by Ctrl + mouse scroll):
public partial class RenderMetafileForm : Form
{
private static Size canvasSize = new Size(300, 200);
private List<PointF> points = new List<PointF>();
private const float pointRadius = 5;
public RenderMetafileForm()
{
InitializeComponent();
metafileViewer.MouseClick += MetafileViewer_MouseClick;
UpdateMetafile();
}
private void MetafileViewer_MouseClick(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && metafileViewer.TryTranslate(e.Location, out var coord))
{
points.Add(coord);
UpdateMetafile();
}
}
private void UpdateMetafile()
{
Graphics refGraph = Graphics.FromHwnd(IntPtr.Zero);
IntPtr hdc = refGraph.GetHdc();
Metafile result;
try
{
result = new Metafile(hdc, new Rectangle(Point.Empty, canvasSize), MetafileFrameUnit.Pixel, EmfType.EmfOnly, "Canvas");
using (var g = Graphics.FromImage(result))
{
foreach (PointF point in points)
g.FillEllipse(Brushes.Navy, point.X - pointRadius, point.Y - pointRadius, pointRadius * 2, pointRadius * 2);
}
}
finally
{
refGraph.ReleaseHdc(hdc);
refGraph.Dispose();
}
Metafile? previous = metafileViewer.Image;
metafileViewer.Image = result;
previous?.Dispose();
}
}
Result:
⚠️ Note: I did not add panning by keyboard or by grabbing the image but you can extract those from the original ImageViewer. Also, I removed DPI-aware scaling but see the ScaleSize extensions in the linked project.
I am recreating a simple tile based game (ref: Javidx9 cpp tile game) on c# winforms and the screen flickers as i move, I have DoubleBuffered = true. i will show an example with textures and one without.
TEXTURES > e.Graphics.DrawImage()
NO TEXTURES > e.Graphics.FillRectangle()
in the code I made a GameManager, CameraManager, PlayerModel, lastly the form OnPaint that draws the game information. the way it works is the GameManager tells the Player to update itself depending on user input (move, jump, ect...), then tells the Camera to update depending on the players position. at first i called the GameManager.Update() from the Paint event but then i separated the Paint event from the GameManager and made the GameManager update asynchronous because the Paint event updates too slow. thats when the problem started.
//GameManager
public void CreateSoloGame(MapModel map)
{
CurrentMap = map;
ResetPlayer();
_inGame = true;
new Task(() =>
{
while (_inGame)
{
Elapsed = _stopwatch.ElapsedMilliseconds;
_stopwatch.Restart();
int i = 0;
Step(Elapsed);
while (i < _gameTime) //_gameTime controls the speed of the game
{
i++;
}
}
}).Start();
}
public void Step(double elapsed)
{
Player.Update(CurrentMap, elapsed);
Camera.SetCamera(Player.Position, CurrentMap);
}
//PlayerModel
public void DetectCollision(MapModel CurrentMap, double Elapsed)
{
//adds velocity to players position
float NextPlayerX = Position.X + (VelX * (float)Elapsed);
float NextPlayerY = Position.Y + (VelY * (float)Elapsed);
//collision detection
OnFloor = false;
if (VelY > 0)
{
//bottom
if (CurrentMap.GetTile((int)(Position.X + .1), (int)(NextPlayerY + 1)) == '#' || CurrentMap.GetTile((int)(Position.X + .9), (int)(NextPlayerY + 1)) == '#')
{
NextPlayerY = (int)NextPlayerY;
VelY = 0;
OnFloor = true;
_jumps = 2;
}
}
else
{
//top
if (CurrentMap.GetTile((int)(Position.X + .1), (int)NextPlayerY) == '#' || CurrentMap.GetTile((int)(Position.X + .9), (int)NextPlayerY) == '#')
{
NextPlayerY = (int)NextPlayerY + 1;
VelY = 0;
}
}
if (VelX < 0)
{
//left
if (CurrentMap.GetTile((int)NextPlayerX, (int)Position.Y) == '#' || CurrentMap.GetTile((int)NextPlayerX, (int)(Position.Y + .9)) == '#')
{
NextPlayerX = (int)NextPlayerX + 1;
VelX = 0;
}
}
else
{
//right
if (CurrentMap.GetTile((int)(NextPlayerX + 1), (int)Position.Y) == '#' || CurrentMap.GetTile((int)(NextPlayerX + 1), (int)(Position.Y + .9)) == '#')
{
NextPlayerX = (int)NextPlayerX;
VelX = 0;
}
}
//updates player position
Position = new PointF(NextPlayerX, NextPlayerY);
}
public void Jump()
{
if (_jumps > 0)
{
VelY = -.06f;
_jumps--;
}
}
public void ReadInput(double elapsed)
{
//resets velocity back to 0 if player isnt moving
if (Math.Abs(VelY) < 0.001f) VelY = 0;
if (Math.Abs(VelX) < 0.001f) VelX = 0;
//sets velocity according to player input - S and W are used for no clip free mode
//if (UserInput.KeyInput[Keys.W]) _playerVelY -= .001f;
//if (UserInput.KeyInput[Keys.S]) _playerVelY += .001f;
if (Input.KEYINPUT[Keys.A]) VelX -= .001f * (float)elapsed;
else if (Input.KEYINPUT[Keys.D]) VelX += .001f * (float)elapsed;
else if (Math.Abs(VelX) > 0.001f && OnFloor) VelX += -0.06f * VelX * (float)elapsed;
//resets jumping
if (!OnFloor)
VelY += .0004f * (float)elapsed;
//limits velocity
//if (_playerVelY <= -.014) _playerVelY = -.014f; //disabled to allow jumps
if (VelY >= .05) VelY = .05f;
if (VelX >= .02 && !Input.KEYINPUT[Keys.ShiftKey]) VelX = .02f;
else if (VelX >= .005 && Input.KEYINPUT[Keys.ShiftKey]) VelX = .005f;
if (VelX <= -.02 && !Input.KEYINPUT[Keys.ShiftKey]) VelX = -.02f;
else if (VelX <= -.005 && Input.KEYINPUT[Keys.ShiftKey]) VelX = -.005f;
}
public void Update(MapModel map, double elapsed)
{
ReadInput(elapsed);
DetectCollision(map, elapsed);
}
//CameraManager
public void SetCamera(PointF center, MapModel map, bool clamp = true)
{
//changes the tile size according to the screen size
TileSize = Input.ClientScreen.Width / Tiles;
//amount of tiles along thier axis
TilesX = Input.ClientScreen.Width / TileSize;
TilesY = Input.ClientScreen.Height / TileSize;
//camera offset
OffsetX = center.X - TilesX / 2.0f;
OffsetY = center.Y - TilesY / 2.0f;
//make sure the offset does not go beyond bounds
if (OffsetX < 0 && clamp) OffsetX = 0;
if (OffsetY < 0 && clamp) OffsetY = 0;
if (OffsetX > map.MapWidth - TilesX && clamp) OffsetX = map.MapWidth - TilesX;
if (OffsetY > map.MapHeight - TilesY && clamp) OffsetY = map.MapHeight - TilesY;
//smooths out movement for tiles
TileOffsetX = (OffsetX - (int)OffsetX) * TileSize;
TileOffsetY = (OffsetY - (int)OffsetY) * TileSize;
}
//Form Paint event
private void Draw(object sender, PaintEventArgs e)
{
Brush b;
Input.ClientScreen = ClientRectangle;
for (int x = -1; x < _camera.TilesX + 1; x++)
{
for (int y = -1; y < _camera.TilesY + 1; y++)
{
switch (_map.GetTile(x + (int)_camera.OffsetX, y + (int)_camera.OffsetY))
{
case '.':
//e.Graphics.DrawImage(sky, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, _camera.TileSize, _camera.TileSize);
//continue;
b = Brushes.MediumSlateBlue;
break;
case '#':
//e.Graphics.DrawImage(block, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, _camera.TileSize, _camera.TileSize);
//continue;
b = Brushes.DarkGray;
break;
case 'o':
b = Brushes.Yellow;
break;
case '%':
b = Brushes.Green;
break;
default:
b = Brushes.MediumSlateBlue;
break;
}
e.Graphics.FillRectangle(b, x * _camera.TileSize - _camera.TileOffsetX, y * _camera.TileSize - _camera.TileOffsetY, (x + 1) * _camera.TileSize, (y + 1) * _camera.TileSize);
}
}
e.Graphics.FillRectangle(Brushes.Purple, (_manager.Player.Position.X - _camera.OffsetX) * _camera.TileSize, (_manager.Player.Position.Y - _camera.OffsetY) * _camera.TileSize, _camera.TileSize, _camera.TileSize);
//e.Graphics.DrawImage(chef, (_manager.Player.Position.X - _camera.OffsetX) * _camera.TileSize, (_manager.Player.Position.Y - _camera.OffsetY) * _camera.TileSize, _camera.TileSize, _camera.TileSize);
Invalidate();
}
P.S. i use winforms because i dont work with GUIs much and its the one im most familiar with and this is just something quick i wanted to try out but i've never had this issue. i tried a couple of things but nothing worked so this is my last resort. if you think i should use another GUI let me know and ill look into it. also if you think my code is ugly lmk why.
Fill the form with a PictureBox and hook into the .Paint() event. For some reason, there is no flicker drawing on a PictureBox compared to drawing on a form.
Also having a game loop improves things a lot. I am getting 600+ fps with my example code.
full code below:
public partial class RunningForm1 : Form
{
static readonly Random rng = new Random();
float offset;
int index;
Queue<int> town;
const int grid = 12;
Color[] pallete;
FpsCounter clock;
#region Windows API - User32.dll
[StructLayout(LayoutKind.Sequential)]
public struct WinMessage
{
public IntPtr hWnd;
public Message msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out WinMessage msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
public RunningForm1()
{
InitializeComponent();
this.pic.Paint += new PaintEventHandler(this.pic_Paint);
this.pic.SizeChanged += new EventHandler(this.pic_SizeChanged);
//Initialize the machine
this.clock = new FpsCounter();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Setup();
System.Windows.Forms.Application.Idle += new EventHandler(OnApplicationIdle);
}
void UpdateMachine()
{
pic.Refresh();
}
#region Main Loop
private void OnApplicationIdle(object sender, EventArgs e)
{
while (AppStillIdle)
{
// Render a frame during idle time (no messages are waiting)
UpdateMachine();
}
}
private bool AppStillIdle
{
get
{
WinMessage msg;
return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
}
#endregion
private void pic_SizeChanged(object sender, EventArgs e)
{
pic.Refresh();
}
private void pic_Paint(object sender, PaintEventArgs e)
{
// Show FPS counter
var fps = clock.Measure();
var text = $"{fps:F2} fps";
var sz = e.Graphics.MeasureString(text, SystemFonts.DialogFont);
var pt = new PointF(pic.Width - 1 - sz.Width - 4, 4);
e.Graphics.DrawString(text, SystemFonts.DialogFont, Brushes.Black, pt);
// draw on e.Graphics
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.TranslateTransform(0, pic.ClientSize.Height - 1);
e.Graphics.ScaleTransform(1, -1);
int wt = pic.ClientSize.Width / (grid - 1);
int ht = pic.ClientSize.Height / (grid + 1);
SolidBrush fill = new SolidBrush(Color.Black);
for (int i = 0; i < town.Count; i++)
{
float x = offset + i * wt;
var building = new RectangleF(
x, 0,
wt, ht * town.ElementAt(i));
fill.Color = pallete[(index + i) % pallete.Length];
e.Graphics.FillRectangle(fill, building);
}
offset -= 0.4f;
if (offset <= -grid - wt)
{
UpdateTown();
offset += wt;
}
}
private void Setup()
{
offset = 0;
index = 0;
town = new Queue<int>();
pallete = new Color[]
{
Color.FromKnownColor(KnownColor.Purple),
Color.FromKnownColor(KnownColor.Green),
Color.FromKnownColor(KnownColor.Yellow),
Color.FromKnownColor(KnownColor.SlateBlue),
Color.FromKnownColor(KnownColor.LightCoral),
Color.FromKnownColor(KnownColor.Red),
Color.FromKnownColor(KnownColor.Blue),
Color.FromKnownColor(KnownColor.LightCyan),
Color.FromKnownColor(KnownColor.Crimson),
Color.FromKnownColor(KnownColor.GreenYellow),
Color.FromKnownColor(KnownColor.Orange),
Color.FromKnownColor(KnownColor.LightGreen),
Color.FromKnownColor(KnownColor.Gold),
};
for (int i = 0; i <= grid; i++)
{
town.Enqueue(rng.Next(grid) + 1);
}
}
private void UpdateTown()
{
town.Dequeue();
town.Enqueue(rng.Next(grid) + 1);
index = (index + 1) % pallete.Length;
}
}
public class FpsCounter
{
public FpsCounter()
{
this.PrevFrame = 0;
this.Frames = 0;
this.PollOverFrames = 100;
this.Clock = Stopwatch.StartNew();
}
/// <summary>
/// Use this method to poll the FPS counter
/// </summary>
/// <returns>The last measured FPS</returns>
public float Measure()
{
Frames++;
PrevFrame++;
var dt = Clock.Elapsed.TotalSeconds;
if (PrevFrame > PollOverFrames || dt > PollOverFrames / 50)
{
LastFps = (float)(PrevFrame / dt);
PrevFrame = 0;
Clock.Restart();
}
return LastFps;
}
public float LastFps { get; private set; }
public long Frames { get; private set; }
private Stopwatch Clock { get; }
private int PrevFrame { get; set; }
/// <summary>
/// The number of frames to average to get a more accurate frame count.
/// The higher this is the more stable the result, but it will update
/// slower. The lower this is, the more chaotic the result of <see cref="Measure()"/>
/// but it will get a new result sooner. Default is 100 frames.
/// </summary>
public int PollOverFrames { get; set; }
}
I want to move the zoomed image with gyroscope. As you can see in below code I can do this thing by hand and I got a good result. I have to write something inside "Gyroscope_ReadingChanged" to convert gyroscope data to the Content.TranslationX and Content.TranslationY. Any help would be appreciated.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:video="clr-namespace:UbSports.Controls"
xmlns:local="clr-namespace:UbSports.Controls"
x:Class="UbSports.Pages.TestPage">
<local:PinchToZoomContainer VerticalOptions="FillAndExpand">
<local:PinchToZoomContainer.Content>
<Image x:Name="image" Source="waterfront.jpg" />
</local:PinchToZoomContainer.Content>
</local:PinchToZoomContainer>
</ContentPage>
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestPage: ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
public class PinchToZoomContainer : ContentView
{
public PinchToZoomContainer()
{
Gyroscope.ReadingChanged += Gyroscope_ReadingChanged;
try
{
Gyroscope.Start(SensorSpeed.UI);
}
catch (FeatureNotSupportedException fnsEx)
{
}
catch (Exception ex)
{
}
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
void Gyroscope_ReadingChanged(object sender, GyroscopeChangedEventArgs e)
{
var data = e.Reading;
if (data.AngularVelocity.X == 0 && data.AngularVelocity.Y == 0) return;
// do something
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
startX = e.TotalX;
startY = e.TotalY;
Content.AnchorX = 0;
Content.AnchorY = 0;
break;
case GestureStatus.Running:
var maxTranslationX = Content.Scale * Content.Width - Content.Width;
Content.TranslationX = Math.Min(0, Math.Max(-maxTranslationX, xOffset + e.TotalX - startX));
var maxTranslationY = Content.Scale * Content.Height - Content.Height;
Content.TranslationY = Math.Min(0, Math.Max(-maxTranslationY, yOffset + e.TotalY - startY));
break;
case GestureStatus.Completed:
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
break;
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
I want to zoom and pan an image in my app.But it is not working perfectly.
I tried this link: https://forums.xamarin.com/discussion/74168/full-screen-image-viewer-with-pinch-to-zoom-pan-to-move-tap-to-show-captions-for-xamarin-forms
Please help me..
Here is my XAML Code
<ContentPage.Content>
<StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" BackgroundColor="Yellow">
<Image Source="download" x:Name="img">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated"/>
<PinchGestureRecognizer PinchUpdated="OnPinchUpdated"/>
<TapGestureRecognizer Tapped="OnTapped"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
</ContentPage.Content>
My mainpage.cs
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale;
private double LastX, LastY;
private double StartX, StartY;
public MainPage()
{
InitializeComponent();
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (Scale > MIN_SCALE)
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
TranslationX = Clamp(LastX + e.TotalX * Scale, -Width / 2, Width / 2);
TranslationY = Clamp(LastY + e.TotalY * Scale, -Height / 2, Height / 2);
break;
}
//switch (e.StatusType)
//{
// case GestureStatus.Started:
// StartX = (1 - AnchorX) * Width;
// StartY = (1 - AnchorY) * Height;
// break;
// case GestureStatus.Running:
// AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
// AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
// break;
//}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
double current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height); //must be called
img.TranslateTo(0, 0, 0, Easing.Linear);
}
private T Clamp<T>(T value, T minimum, T maximum) where T : IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
When I zoom the image ,the image position look like this
Image gestures is not working perfectly.When double tap the image ,zoom in and zoom out work perfectly.
When my windows form loads the tool strip menu item bar is there but the items inside it are not shown until I hover over them or press alt.
Also, my colordialog box wont open until after I click the change color icon in my menu and then press Alt.
I've had a look through the properties to make sure there is no hide property on or something but I can't seem to find anything.
I've posted some links below to show you what's happening.
When it opens up: http://gyazo.com/c1f4fa4d27e3f54a65b1f8a3da6cd0da
When I press alt or hover over menu: http://gyazo.com/c86c5dfb723647c025f89a0c14da6766
Not sure what code to put in.
Changing color bit:
private void changeColourToolStripMenuItem_Click(object sender, EventArgs e)
{
DialogResult result = colorDialog1.ShowDialog();
}
I just dragged the elements on from the toolbox.
All Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Assignment
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
init();
start();
this.DoubleBuffered = true;
}
//code to convert HSB to RGB from HSB.cs. All your code so i made it take up less space.
public struct HSBColor
{
float h;
float s;
float b;
int a;
public HSBColor(float h, float s, float b) { this.a = 0xff; this.h = Math.Min(Math.Max(h, 0), 255); this.s = Math.Min(Math.Max(s, 0), 255); this.b = Math.Min(Math.Max(b, 0), 255); }
public HSBColor(int a, float h, float s, float b) { this.a = a; this.h = Math.Min(Math.Max(h, 0), 255); this.s = Math.Min(Math.Max(s, 0), 255); this.b = Math.Min(Math.Max(b, 0), 255); }
public float H { get { return h; } }
public float S { get { return s; } }
public float B { get { return b; } }
public int A { get { return a; } }
public Color Color { get { return FromHSB(this); } }
public static Color FromHSB(HSBColor hsbColor)
{
float r = hsbColor.b;
float g = hsbColor.b;
float b = hsbColor.b;
if (hsbColor.s != 0)
{
float max = hsbColor.b; float dif = hsbColor.b * hsbColor.s / 255f; float min = hsbColor.b - dif; float h = hsbColor.h * 360f / 255f;
if (h < 60f) { r = max; g = h * dif / 60f + min; b = min; }
else if (h < 120f) { r = -(h - 120f) * dif / 60f + min; g = max; b = min; }
else if (h < 180f) { r = min; g = max; b = (h - 120f) * dif / 60f + min; }
else if (h < 240f) { r = min; g = -(h - 240f) * dif / 60f + min; b = max; }
else if (h < 300f) { r = (h - 240f) * dif / 60f + min; g = min; b = max; }
else if (h <= 360f) { r = max; g = min; b = -(h - 360f) * dif / 60 + min; }
else { r = 0; g = 0; b = 0; }
}
return Color.FromArgb(hsbColor.a, (int)Math.Round(Math.Min(Math.Max(r, 0), 255)), (int)Math.Round(Math.Min(Math.Max(g, 0), 255)), (int)Math.Round(Math.Min(Math.Max(b, 0), 255)));
}
}
private const int MAX = 256; // max iterations
private const double SX = -2.025; // start value real
private const double SY = -1.125; // start value imaginary
private const double EX = 0.6; // end value real
private const double EY = 1.125; // end value imaginary
private static int x1, y1, xs, ys, xe, ye;
private static double xstart, ystart, xende, yende, xzoom, yzoom;
private static float xy;
private int c = 0;
//private Image picture; Taken out, not needed
// create rectangle variable JGB
Rectangle rec;
private Graphics g1;
//private Cursor c1, c2; Taken out, not needed
private System.Drawing.Bitmap bitmap;
public void init()
{
//setSize(640, 480); changed this code to JGB:
this.Size = new Size(640, 480);
// Taken all lines out below. Not needed.
/*finished = false;
addMouseListener(this);
addMouseMotionListener(this);
c1 = new Cursor(Cursor.WAIT_CURSOR);
c2 = new Cursor(Cursor.CROSSHAIR_CURSOR); */
x1 = 640;
y1 = 480;
xy = (float)x1 / (float)y1;
//picture = createImage(x1, y1); Taken out and replaced with JGB:
bitmap = new Bitmap(x1, y1);
//g1 = picture.getGraphics(); changed to get my bitmap
g1 = Graphics.FromImage(bitmap);
//finished = true; Finished variable deleted so not needed
}
//Code below didnt appear to do anything so i deleted it
/*public void destroy() // delete all instances
{
if (finished)
{
removeMouseListener(this);
removeMouseMotionListener(this);
picture = null;
g1 = null;
c1 = null;
c2 = null;
System.gc(); // garbage collection
}
} */
public void start()
{
//action = false;
//rectangle = false;
initvalues();
// added dialog box for instance loading and save varaibles needed for position and zoom to text file
DialogResult dialog = MessageBox.Show("Would You Like to Load Your Last Instance?", "Load Instance?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (dialog == DialogResult.Yes)
{
string[] lines = System.IO.File.ReadAllLines(#"C:\Users\Public\Writelines.txt");
xzoom = System.Convert.ToDouble(lines[0]);
yzoom = System.Convert.ToDouble(lines[1]);
xstart = System.Convert.ToDouble(lines[2]);
ystart = System.Convert.ToDouble(lines[3]);
}
else
{
xzoom = (xende - xstart) / (double)x1;
yzoom = (yende - ystart) / (double)y1;
}
mandelbrot();
}
public void stop()
{
}
/*public void paint(Graphics g, PaintEventArgs e)
{
update(g);
}
public void update(Graphics g)
{
//g.DrawImage(picture, 0, 0);
}*/
private void mandelbrot()
{
int x, y;
float h, b, alt = 0.0f;
Color color;
Pen pen = new Pen(Color.Black);
for (x = 0; x < x1; x += 2)
for (y = 0; y < y1; y++)
{
h = pointcolour(xstart + xzoom * (double)x, ystart + yzoom * (double)y, c);
if (h != alt)
{
b = 1.0f - h * h;
color = HSBColor.FromHSB(new HSBColor(h * 255, 0.8f * 255, b * 255));
pen = new Pen(color);
alt = h;
}
g1.DrawLine(pen, x, y, x + 1, y);
}
}
private float pointcolour(double xwert, double ywert, int j)
{
double r = 0.0, i = 0.0, m = 0.0;
// int j = 0;
while ((j < MAX) && (m < 4.0))
{
j++;
m = r * r - i * i;
i = 2.0 * r * i + ywert;
r = m + xwert;
}
return (float)j / (float)MAX;
}
private void initvalues()
{
xstart = SX;
ystart = SY;
xende = EX;
yende = EY;
if ((float)((xende - xstart) / (yende - ystart)) != xy)
xstart = xende - (yende - ystart) * (double)xy;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g1 = e.Graphics;
g1.DrawImage(bitmap, 0, 0, x1, y1);
using (Pen pen = new Pen(Color.White, 2))
{
e.Graphics.DrawRectangle(pen, rec);
}
Invalidate();
}
//added load method
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
xe = e.X;
ye = e.Y;
if (xs < xe)
{
if (ys < ye) rec = new Rectangle(xs, ys, (xe - xs), (ye - ys));
else rec = new Rectangle(xs, ye, (xe - xs), (ys - ye));
}
else
{
if (ys < ye) rec = new Rectangle(xe, ys, (xs - xe), (ye - ys));
else rec = new Rectangle(xe, ye, (xs - xe), (ys - ye));
}
this.Invalidate();
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
// e.consume();
xs = e.X;
ys = e.Y; // starting point y
this.Invalidate();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
rec = new Rectangle(0, 0, 0, 0);
if (e.Button == MouseButtons.Left)
{
int z, w;
//e.consume();
//xe = e.X;
//ye = e.Y;
if (xs > xe)
{
z = xs;
xs = xe;
xe = z;
}
if (ys > ye)
{
z = ys;
ys = ye;
ye = z;
}
w = (xe - xs);
z = (ye - ys);
if ((w < 2) && (z < 2)) initvalues();
else
{
if (((float)w > (float)z * xy)) ye = (int)((float)ys + (float)w / xy);
else xe = (int)((float)xs + (float)z * xy);
xende = xstart + xzoom * (double)xe;
yende = ystart + yzoom * (double)ye;
xstart += xzoom * (double)xs;
ystart += yzoom * (double)ys;
}
xzoom = (xende - xstart) / (double)x1;
yzoom = (yende - ystart) / (double)y1;
mandelbrot();
string stringxzoom = xzoom.ToString();
string stringyzoom = yzoom.ToString();
string stringystart = ystart.ToString();
string stringxstart = xstart.ToString();
string[] lines = { stringxzoom, stringyzoom, stringxstart, stringystart };
System.IO.File.WriteAllLines(#"C:\Users\Public\Writelines.txt", lines);
this.Invalidate();
//Repaint();
}
}
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
dialog.InitialDirectory =System.Environment.GetFolderPath(Environment.SpecialFolder.Personal);
dialog.Title = "Save Image";
dialog.FileName = "";
dialog.Filter = "JPEG|*.jpg";
dialog.ShowDialog();
}
private void restartToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Restart();
}
private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void changeColourToolStripMenuItem_Click(object sender, EventArgs e)
{
colorDialog1.ShowDialog();
}
private void menuToolStripMenuItem_Click(object sender, EventArgs e)
{
}
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
}
}
}
Ok so after hours of trying to figure this out i found the solution. A random invalidate was messing it up.
Thanks anyway to #Sjips for the fast help.