Linux - Create Animated Gif with Pan and Zoom

Create sliding video from pattern/tile image

You can do that with my Imagemagick (bash unix) script, overlapcrop. It will do overlap cropping and then make your animation as one output option. See http://www.fmwconcepts.com/imagemagick/overlapcrop/index.php

Input:

Sample Image

# do tiling to a 600x300 larger image as an example:

magick -size 600x300 tile:pattern_blue_and_white.jpg pattern_long.jpg

# do overlap crop and make animation:

overlapcropxx -s 300x300 -o 290x0 -m animation -z 2 -d 30 pattern_long.jpg pattern_animation.gif

Result:

Sample Image

Android ImageView Zoom-in and Zoom-Out

Make two java classes

Zoom class

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;

public class Zoom extends View {

private Drawable image;
ImageButton img,img1;
private int zoomControler=20;

public Zoom(Context context){
super(context);

image=context.getResources().getDrawable(R.drawable.j);
//image=context.getResources().getDrawable(R.drawable.icon);

setFocusable(true);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//here u can control the width and height of the images........ this line is very important
image.setBounds((getWidth()/2)-zoomControler, (getHeight()/2)-zoomControler, (getWidth()/2)+zoomControler, (getHeight()/2)+zoomControler);
image.draw(canvas);
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

if(keyCode==KeyEvent.KEYCODE_DPAD_UP){
// zoom in
zoomControler+=10;
}
if(keyCode==KeyEvent.KEYCODE_DPAD_DOWN){
// zoom out
zoomControler-=10;
}
if(zoomControler<10){
zoomControler=10;
}

invalidate();
return true;
}
}

make second class

import android.app.Activity;
import android.os.Bundle;

public class Zoomexample extends Activity {
/** Called when the activity is first created. */

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(new Zoom(this));
}
}

Live Script with animation

The example code as posted in the question produces a rotating plot as of MATLAB 2019a. It does not work yet in 2018b. The release notes for 2019a mention that

You can enable for-loop animations in the Live Editor to show changes
in plotted data over time. To enable animations in the Live Editor,
set the matlab.editor.AllowFigureAnimations setting to true:

s = settings;
s.matlab.editor.AllowFigureAnimation.PersonalValue = true;

Running these two lines before the example script will yield the expected behaviour.

How to implement canvas panning with Fabric.js

An easy way to pan a Fabric canvas in response to mouse movement is to calculate the cursor displacement between mouse events and pass it to relativePan.

Observe how we can use the screenX and screenY properties of the previous mouse event to calculate the relative position of the current mouse event:

function startPan(event) {
if (event.button != 2) {
return;
}
var x0 = event.screenX,
y0 = event.screenY;
function continuePan(event) {
var x = event.screenX,
y = event.screenY;
fc.relativePan({ x: x - x0, y: y - y0 });
x0 = x;
y0 = y;
}
function stopPan(event) {
$(window).off('mousemove', continuePan);
$(window).off('mouseup', stopPan);
};
$(window).mousemove(continuePan);
$(window).mouseup(stopPan);
$(window).contextmenu(cancelMenu);
};
function cancelMenu() {
$(window).off('contextmenu', cancelMenu);
return false;
}
$(canvasWrapper).mousedown(startPan);

We start panning on mousedown and continue panning on mousemove. On mouseup, we cancel the panning; we also cancel the mouseup-cancelling function itself.

The right-click menu, also known as the context menu, is cancelled by returning false. The menu-cancelling function also cancels itself. Thus, the context menu will work if you subsequently click outside the canvas wrapper.

Here is a page demonstrating this approach:

http://michaellaszlo.com/so/fabric-pan/

You will see three images on a Fabric canvas (it may take a moment or two for the images to load). You'll be able to use the standard Fabric functionality. You can left-click on the images to move them around, stretch them, and rotate them. But when you right-click within the canvas container, you pan the whole Fabric canvas with the mouse.

Google Maps SDK for Android: Smoothly animating the camera to a new location, rendering all the tiles along the way

Here's my attempt using your utility frame player.

A few notes:

  • The zoom value is interpolated based on the total steps (set at 500 here) and given the start and stop values.
  • A Google Maps utility is used to compute the next lat lng based on a fractional distance: SphericalUtil.interpolate.
  • The fractional distance should not be a linear function to reduce the introduction of new tiles. In other words, at higher zooms (closer in) the camera moves in shorter distances and the amount of camera movement increases exponentially (center-to-center) while zooming out. This requires a bit more explanation...
  • As you can see the traversal is split into two - reversing the exponential function of the distance movement.
  • The "max" zoom (bad name) which is the furthest out can be a function of the total distance - computed to encompass the whole path at the midpoint. For now it's hard coded to 4 for this case.
  • Note the maps animate function cannot be used as it introduces its own bouncing ball effect on each step which is undesirable. So given a fair number of steps the move function can be used.
  • This method attempts to minimize tile loading per step but ultimately the TileLoader is the limiting factor for viewing which cannot monitored (easily).

animateCameraToPosition

// flag to control the animate callback (at completion).
boolean done = false;

private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
CameraPosition currPosition = gMap.getCameraPosition();
LatLng currLatLng = currPosition.target;

//meters_per_pixel = 156543.03392 * Math.cos(latLng.lat() * Math.PI / 180) / Math.pow(2, zoom)
int maxSteps = 500;
// number of steps between start and midpoint and midpoint and end
int stepsMid = maxSteps / 2;

// current zoom
float initz = currPosition.zoom;
//TODO maximum zoom (can be computed from overall distance) such that entire path
// is visible at midpoint.
float maxz = 4.0f;
float finalz = targetZoom;

CameraUpdateAnimator animator = new CameraUpdateAnimator(gMap, () -> {
if (!done) {
gMap.animateCamera(CameraUpdateFactory.
zoomTo(targetZoom), 5000, null);
}
done = true;

});

// loop from start to midpoint

for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = initz - ((initz - maxz) / stepsMid) * i;

// Compute fractional distance using an exponential function such that for the first
// half the fraction delta advances slowly and accelerates toward midpoint.
double ff = (i * (Math.pow(2,maxz) / Math.pow(2,z))) / maxSteps;

LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);
animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}

// loop from midpoint to final
for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = maxz + ((finalz - maxz) / stepsMid) * i;
double ff = (maxSteps - ((i+stepsMid) * ( (Math.pow(2,maxz) / Math.pow(2,z)) ))) / (double)maxSteps;

LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);

animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}

animator.add(CameraUpdateFactory.newLatLngZoom(
targetLatLng, targetZoom), true, 0);

//

animator.execute();
}

Test Code

I tested with these two points (and code) from Statue Of Liberty to a point on the west coast:

gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(40.68924, -74.04454), 13.0f));

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
animateCameraToPosition(new LatLng(33.899832, -118.020450), 13.0f);
}
}, 5000);

CameraUpdateAnimator Mods

I modified the camera update animator slightly:

public void execute() {
mMap.setOnCameraIdleListener(this);
executeNext();
}

private void executeNext() {
if (cameraUpdates.isEmpty()) {
mMap.setOnCameraIdleListener(mOnCameraIdleListener);
mOnCameraIdleListener.onCameraIdle();
} else {
final Animation animation = cameraUpdates.remove(0);
// This optimization is likely unnecessary since I think the
// postDelayed does the same on a delay of 0 - execute immediately.
if (animation.mDelay > 0) {
new Handler().postDelayed(() -> {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}, animation.mDelay);
} else {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}
}
}

Before Sample

Using

// assume initial (40.68924, -74.04454) z=13.0f
gMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(33.899832,-118.020450), 13.0f), 30000, null);

After Samples

These are recorded from an emulator. I also sideloaded onto my phone (Samsumg SM-G960U) with similar results (using 1000 steps 0 delay).

So I don't think this meets your requirements entirely: there are some "ambiguous tiles" as they are brought in from the west.

Statue of Liberty - to - somewhere near San Diego

500 Steps 0 delay

100 Steps 0 delay

50 Steps 100MS delay



Diagnostics

It is in some ways useful to have insight into what Maps is doing with tiles. Insight can be provided by installing a simple UrlTileProvider and log the requests. This implementation fetches the google tiles though they are lower resolution that is normally seen.

To do this the following is required:

    // Turn off this base map and install diagnostic tile provider
gMap.setMapType(GoogleMap.MAP_TYPE_NONE);
gMap.addTileOverlay(new TileOverlayOptions().tileProvider(new MyTileProvider(256,256)).fadeIn(true));

And define the diagnostic file provider

public class MyTileProvider extends UrlTileProvider {

public MyTileProvider(int i, int i1) {
super(i, i1);
}

@Override
public URL getTileUrl(int x, int y, int zoom) {

Log.i("tiles","x="+x+" y="+y+" zoom="+zoom);

try {
return new URL("http://mt1.google.com/vt/lyrs=m&x="+x+"&y="+y+"&z="+zoom);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}

}
}

You'll notice right away that tile layers are always defined in integral units (int). The fractional zooms which are supplied in the zoom (e.g. LatLngZoom work strictly with the in-memory images - good to know.'

Here's a sample for completeness:

// initial zoom 
x=2411 y=3080 zoom=13
x=2410 y=3080 zoom=13
x=2411 y=3081 zoom=13
x=2410 y=3081 zoom=13
x=2411 y=3079 zoom=13
x=2410 y=3079 zoom=13

And at max:

x=9 y=12 zoom=5
x=8 y=12 zoom=5
x=9 y=11 zoom=5
x=8 y=11 zoom=5
x=8 y=13 zoom=5
x=9 y=13 zoom=5
x=7 y=12 zoom=5
x=7 y=11 zoom=5
x=7 y=13 zoom=5
x=8 y=10 zoom=5
x=9 y=10 zoom=5
x=7 y=10 zoom=5

Here's a chart of the zooms (y-axis) at each invocation of tiler (x-axis). Each zoom layer are roughly the same count which imo is what is desired. The full-out zoom appears twice as long because that's the midpoint repeating. There are a few anomalies though which require explaining (e.g. at around 110).

This is a chart of "zoom" as logged by the tile provider. So each x-axis point would represent a single tile fetch.

Sample Image



Related Topics



Leave a reply



Submit