Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial matrix #18

Open
aloisdeniel opened this issue May 12, 2020 · 3 comments
Open

Initial matrix #18

aloisdeniel opened this issue May 12, 2020 · 3 comments

Comments

@aloisdeniel
Copy link

It is pretty common to be able to edit an already transformed image.

For this use case, it would be needed to initialize the widget with an initial transform.

MatrixGestureDetector(
    initialMatrix: myMatrix
    onMatrixUpdate: ...
    child: ...
  )
@ezorrio
Copy link

ezorrio commented May 31, 2020

+1 on this, might be very useful :)

@R1Snake
Copy link

R1Snake commented Aug 20, 2020

I am searching since a week for a solution to set an initial matrix.

Thump up to get this issue done.

Has anyone a quick and dirty solution for this?

@R1Snake
Copy link

R1Snake commented Aug 21, 2020

I added a initMatrix method for this. I created a new class called: MyMatrixGestureDetector

import 'dart:math';

import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart';

typedef MyMatrixGestureDetectorCallback = void Function(
Matrix4 matrix,
Matrix4 translationDeltaMatrix,
Matrix4 scaleDeltaMatrix,
Matrix4 rotationDeltaMatrix);

/// [MatrixGestureDetector] detects translation, scale and rotation gestures
/// and combines them into [Matrix4] object that can be used by [Transform] widget
/// or by low level [CustomPainter] code. You can customize types of reported
/// gestures by passing [shouldTranslate], [shouldScale] and [shouldRotate]
/// parameters.
///
class MyMatrixGestureDetector extends StatefulWidget {
/// [Matrix4] change notification callback
///
final MyMatrixGestureDetectorCallback onMatrixUpdate;

/// The [child] contained by this detector.
///
/// {@macro flutter.widgets.child}
///
final Widget child;

/// Whether to detect translation gestures during the event processing.
///
/// Defaults to true.
///
final bool shouldTranslate;

final Matrix4 initMatrix;

/// Whether to detect scale gestures during the event processing.
///
/// Defaults to true.
///
final bool shouldScale;

/// Whether to detect rotation gestures during the event processing.
///
/// Defaults to true.
///
final bool shouldRotate;

/// Whether [ClipRect] widget should clip [child] widget.
///
/// Defaults to true.
///
final bool clipChild;

/// When set, it will be used for computing a "fixed" focal point
/// aligned relative to the size of this widget.
final Alignment focalPointAlignment;

const MyMatrixGestureDetector({
Key key,
@required this.onMatrixUpdate,
@required this.child,
@required this.initMatrix,
this.shouldTranslate = true,
this.shouldScale = true,
this.shouldRotate = true,
this.clipChild = true,
this.focalPointAlignment,
}) : assert(onMatrixUpdate != null),
assert(child != null),
super(key: key);

@OverRide
_MyMatrixGestureDetectorState createState() =>
_MyMatrixGestureDetectorState();

///
/// Compose the matrix from translation, scale and rotation matrices - you can
/// pass a null to skip any matrix from composition.
///
/// If [matrix] is not null the result of the composing will be concatenated
/// to that [matrix], otherwise the identity matrix will be used.
///
static Matrix4 compose(Matrix4 matrix, Matrix4 translationMatrix,
Matrix4 scaleMatrix, Matrix4 rotationMatrix) {
if (matrix == null) matrix = Matrix4.identity();
if (translationMatrix != null) matrix = translationMatrix * matrix;
if (scaleMatrix != null) matrix = scaleMatrix * matrix;
if (rotationMatrix != null) matrix = rotationMatrix * matrix;
return matrix;
}

///
/// Decomposes [matrix] into [MatrixDecomposedValues.translation],
/// [MatrixDecomposedValues.scale] and [MatrixDecomposedValues.rotation] components.
///
static MyMatrixDecomposedValues decomposeToValues(Matrix4 matrix) {
var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]);
Offset translation = Offset(array[0], array[1]);
Offset delta = Offset(array[3] - array[0], array[4] - array[1]);
double scale = delta.distance;
double rotation = delta.direction;
return MyMatrixDecomposedValues(translation, scale, rotation);
}
}

class _MyMatrixGestureDetectorState extends State {
Matrix4 translationDeltaMatrix = Matrix4.identity();
Matrix4 scaleDeltaMatrix = Matrix4.identity();
Matrix4 rotationDeltaMatrix = Matrix4.identity();
Matrix4 matrix = Matrix4.identity();

@OverRide
Widget build(BuildContext context) {
Widget child =
widget.clipChild ? ClipRect(child: widget.child) : widget.child;
return GestureDetector(
onScaleStart: onScaleStart,
onScaleUpdate: onScaleUpdate,
child: child,
);
}

@OverRide
void initState() {
super.initState();
matrix = widget.initMatrix;
}

_ValueUpdater translationUpdater = _ValueUpdater(
onUpdate: (oldVal, newVal) => newVal - oldVal,
);
_ValueUpdater rotationUpdater = _ValueUpdater(
onUpdate: (oldVal, newVal) => newVal - oldVal,
);
_ValueUpdater scaleUpdater = _ValueUpdater(
onUpdate: (oldVal, newVal) => newVal / oldVal,
);

void onScaleStart(ScaleStartDetails details) {
translationUpdater.value = details.focalPoint;
rotationUpdater.value = double.nan;
scaleUpdater.value = 1.0;
}

void onScaleUpdate(ScaleUpdateDetails details) {
//print(details);
translationDeltaMatrix = Matrix4.identity();
scaleDeltaMatrix = Matrix4.identity();
rotationDeltaMatrix = Matrix4.identity();

// handle matrix translating
if (widget.shouldTranslate) {
  Offset translationDelta = translationUpdater.update(details.focalPoint);
  translationDeltaMatrix = _translate(translationDelta);
  print(matrix);
  matrix = translationDeltaMatrix * matrix;
}

Offset focalPoint;
if (widget.focalPointAlignment != null) {
  focalPoint = widget.focalPointAlignment.alongSize(context.size);
} else {
  RenderBox renderBox = context.findRenderObject();
  focalPoint = renderBox.globalToLocal(details.focalPoint);
}

// handle matrix scaling
if (widget.shouldScale && details.scale != 1.0) {
  double scaleDelta = scaleUpdater.update(details.scale);
  scaleDeltaMatrix = _scale(scaleDelta, focalPoint);

  matrix = scaleDeltaMatrix * matrix;
}

// handle matrix rotating
if (widget.shouldRotate && details.rotation != 0.0) {
  if (rotationUpdater.value.isNaN) {
    rotationUpdater.value = details.rotation;
  } else {
    double rotationDelta = rotationUpdater.update(details.rotation);
    rotationDeltaMatrix = _rotate(rotationDelta, focalPoint);
    matrix = rotationDeltaMatrix * matrix;
  }
}

widget.onMatrixUpdate(
    matrix, translationDeltaMatrix, scaleDeltaMatrix, rotationDeltaMatrix);

}

Matrix4 _translate(Offset translation) {
var dx = translation.dx;
var dy = translation.dy;

//  ..[0]  = 1       # x scale
//  ..[5]  = 1       # y scale
//  ..[10] = 1       # diagonal "one"
//  ..[12] = dx      # x translation
//  ..[13] = dy      # y translation
//  ..[15] = 1       # diagonal "one"
return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);

}

Matrix4 _scale(double scale, Offset focalPoint) {
var dx = (1 - scale) * focalPoint.dx;
var dy = (1 - scale) * focalPoint.dy;

//  ..[0]  = scale   # x scale
//  ..[5]  = scale   # y scale
//  ..[10] = 1       # diagonal "one"
//  ..[12] = dx      # x translation
//  ..[13] = dy      # y translation
//  ..[15] = 1       # diagonal "one"
return Matrix4(scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);

}

Matrix4 _rotate(double angle, Offset focalPoint) {
var c = cos(angle);
var s = sin(angle);
var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy;
var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx;

//  ..[0]  = c       # x scale
//  ..[1]  = s       # y skew
//  ..[4]  = -s      # x skew
//  ..[5]  = c       # y scale
//  ..[10] = 1       # diagonal "one"
//  ..[12] = dx      # x translation
//  ..[13] = dy      # y translation
//  ..[15] = 1       # diagonal "one"
return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);

}
}

typedef _OnUpdate = T Function(T oldValue, T newValue);

class _ValueUpdater {
final _OnUpdate onUpdate;
T value;

_ValueUpdater({this.onUpdate});

T update(T newValue) {
T updated = onUpdate(value, newValue);
value = newValue;
return updated;
}
}

class MyMatrixDecomposedValues {
/// Translation, in most cases useful only for matrices that are nothing but
/// a translation (no scale and no rotation).
final Offset translation;

/// Scaling factor.
final double scale;

/// Rotation in radians, (-pi..pi) range.
final double rotation;

MyMatrixDecomposedValues(this.translation, this.scale, this.rotation);

@OverRide
String toString() {
return 'MatrixDecomposedValues(translation: $translation, scale: ${scale.toStringAsFixed(3)}, rotation: ${rotation.toStringAsFixed(3)})';
}
}

In your build method:

return MyMatrixGestureDetector(
initMatrix: widget.appearience,
onMatrixUpdate: (m, tm, sm, rm) {
notifier.value = m;
print(m);
},
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: widget.child,
);
},
),
);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants