@rafaelaugusto/jolt 0.1.0-beta.6 → 0.1.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/image-effect-native.d.ts +26 -0
- package/dist/image-effect-native.d.ts.map +1 -0
- package/dist/image-effect-native.js +73 -0
- package/dist/image-effect-native.js.map +1 -0
- package/dist/image-effect.d.ts +50 -0
- package/dist/image-effect.d.ts.map +1 -0
- package/dist/image-effect.js +290 -0
- package/dist/image-effect.js.map +1 -0
- package/dist/image-owner.d.ts +3 -0
- package/dist/image-owner.d.ts.map +1 -0
- package/dist/image-owner.js +11 -0
- package/dist/image-owner.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/liquid.d.ts.map +1 -1
- package/dist/liquid.js +4 -0
- package/dist/liquid.js.map +1 -1
- package/dist/zoom-native.d.ts +30 -0
- package/dist/zoom-native.d.ts.map +1 -0
- package/dist/zoom-native.js +56 -0
- package/dist/zoom-native.js.map +1 -0
- package/dist/zoomable.d.ts +48 -0
- package/dist/zoomable.d.ts.map +1 -0
- package/dist/zoomable.js +249 -0
- package/dist/zoomable.js.map +1 -0
- package/package.json +2 -1
- package/platforms/android/java/org/rafaelaugusto/jolt/ImageEffect.java +288 -0
- package/platforms/android/java/org/rafaelaugusto/jolt/PhotoZoom.java +250 -0
- package/platforms/android/proguard-rules.pro +4 -0
- package/platforms/ios/build.xcconfig +1 -0
- package/platforms/ios/src/JoltImageEffect.h +25 -0
- package/platforms/ios/src/JoltImageEffect.mm +242 -0
- package/platforms/ios/src/JoltPhotoZoom.h +23 -0
- package/platforms/ios/src/JoltPhotoZoom.m +229 -0
- package/platforms/ios/src/module.modulemap +5 -0
- package/src/android-plugin.d.ts +78 -0
- package/src/image-effect-native.ts +129 -0
- package/src/image-effect.ts +336 -0
- package/src/image-owner.ts +12 -0
- package/src/index.ts +9 -0
- package/src/ios-plugin.d.ts +59 -0
- package/src/liquid.ts +4 -0
- package/src/zoom-native.ts +124 -0
- package/src/zoomable.ts +330 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#import "JoltImageEffect.h"
|
|
2
|
+
#import <MetalKit/MetalKit.h>
|
|
3
|
+
#import <simd/simd.h>
|
|
4
|
+
|
|
5
|
+
static const char *JoltImageShader = R"METAL(
|
|
6
|
+
#include <metal_stdlib>
|
|
7
|
+
using namespace metal;
|
|
8
|
+
struct Vertex {float4 position [[position]]; float2 uv;};
|
|
9
|
+
struct Params {float2 size; float2 origin; float2 displacement; float progress; float amplitude; float radius; float seed; float mode;};
|
|
10
|
+
vertex Vertex joltVertex(uint id [[vertex_id]]) {
|
|
11
|
+
float2 p[4]={float2(-1,-1),float2(1,-1),float2(-1,1),float2(1,1)};
|
|
12
|
+
float2 uv[4]={float2(0,1),float2(1,1),float2(0,0),float2(1,0)};
|
|
13
|
+
return {float4(p[id],0,1),uv[id]};
|
|
14
|
+
}
|
|
15
|
+
fragment float4 joltImage(Vertex in [[stage_in]], constant Params &u [[buffer(0)]],
|
|
16
|
+
texture2d<float> source [[texture(0)]], texture2d<float> destination [[texture(1)]]) {
|
|
17
|
+
constexpr sampler s(coord::normalized,address::clamp_to_edge,filter::linear);
|
|
18
|
+
float2 p=in.uv*u.size,q=p;
|
|
19
|
+
if(u.mode<1.5) {
|
|
20
|
+
float2 v=p-u.origin; float d=length(v);
|
|
21
|
+
float edge=clamp(min(min(p.x,u.size.x-p.x),min(p.y,u.size.y-p.y))/max(u.amplitude*2.,1.),0.,1.);
|
|
22
|
+
if(u.mode<.5) {
|
|
23
|
+
float wave=d-u.progress*length(u.size);
|
|
24
|
+
float a=sin(wave/max(u.radius*.12,1.))*exp(-pow(wave/max(u.radius,1.),2.))*u.amplitude*sin(u.progress*3.14159265);
|
|
25
|
+
q-=v/max(d,1.)*a*edge;
|
|
26
|
+
} else q-=u.displacement*exp(-d*d/max(u.radius*u.radius,1.))*edge;
|
|
27
|
+
return source.sample(s,q/u.size);
|
|
28
|
+
}
|
|
29
|
+
if(u.progress<=0.) return source.sample(s,in.uv);
|
|
30
|
+
if(u.progress>=1.) return destination.sample(s,in.uv);
|
|
31
|
+
float n=fract(sin(dot(floor(p/3.),float2(12.9898,78.233))+u.seed)*43758.5453);
|
|
32
|
+
float a=smoothstep(n-.08,n+.08,u.progress*1.16-.08);
|
|
33
|
+
return mix(source.sample(s,in.uv),destination.sample(s,in.uv),a);
|
|
34
|
+
}
|
|
35
|
+
)METAL";
|
|
36
|
+
|
|
37
|
+
typedef struct {
|
|
38
|
+
vector_float2 size, origin, displacement;
|
|
39
|
+
float progress, amplitude, radius, seed, mode;
|
|
40
|
+
} JoltImageParams;
|
|
41
|
+
|
|
42
|
+
@interface JoltImageEffect () <MTKViewDelegate, UIGestureRecognizerDelegate>
|
|
43
|
+
@property(nonatomic, weak) UIImageView *image;
|
|
44
|
+
@property(nonatomic, strong) UIImage *original;
|
|
45
|
+
@property(nonatomic, strong) MTKView *surface;
|
|
46
|
+
@property(nonatomic, strong) id<MTLCommandQueue> queue;
|
|
47
|
+
@property(nonatomic, strong) id<MTLRenderPipelineState> pipeline;
|
|
48
|
+
@property(nonatomic, strong) id<MTLTexture> sourceTexture, destinationTexture;
|
|
49
|
+
@property(nonatomic, strong) UIPanGestureRecognizer *pan;
|
|
50
|
+
@property(nonatomic, copy) void (^listener)(BOOL,double);
|
|
51
|
+
@property(nonatomic) BOOL originalInteraction, disposed, playing, settling;
|
|
52
|
+
@property(nonatomic, readwrite) BOOL paused;
|
|
53
|
+
@property(nonatomic, readwrite) double progress;
|
|
54
|
+
@property(nonatomic, readwrite) NSUInteger frames;
|
|
55
|
+
@property(nonatomic, readwrite) double longestFrame;
|
|
56
|
+
@property(nonatomic, readwrite) NSUInteger textureBytes;
|
|
57
|
+
@property(nonatomic) double duration, goal, anchor, startProgress, remaining, previousFrame;
|
|
58
|
+
@property(nonatomic) vector_float2 settleStart;
|
|
59
|
+
@property(nonatomic) JoltImageParams params;
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
@implementation JoltImageEffect
|
|
63
|
+
+ (instancetype)createWithView:(UIImageView *)view destination:(UIImage *)destination mode:(NSInteger)mode
|
|
64
|
+
originX:(double)x originY:(double)y amplitude:(double)amplitude radius:(double)radius
|
|
65
|
+
duration:(double)duration seed:(double)seed listener:(void (^)(BOOL,double))listener {
|
|
66
|
+
if(!view.image.CGImage || view.image.images.count || view.image.renderingMode==UIImageRenderingModeAlwaysTemplate || !view.window) return nil;
|
|
67
|
+
CGFloat scale=view.window.screen.scale;
|
|
68
|
+
NSUInteger w=(NSUInteger)ceil(view.bounds.size.width*scale),h=(NSUInteger)ceil(view.bounds.size.height*scale);
|
|
69
|
+
if(!w || !h || w*h>4194304 || (mode==2 && !destination.CGImage)) return nil;
|
|
70
|
+
id<MTLDevice> device=MTLCreateSystemDefaultDevice();
|
|
71
|
+
if(!device) return nil;
|
|
72
|
+
static id<MTLRenderPipelineState> cached;
|
|
73
|
+
static dispatch_once_t once;
|
|
74
|
+
dispatch_once(&once, ^{
|
|
75
|
+
NSError *error=nil;
|
|
76
|
+
id<MTLLibrary> library=[device newLibraryWithSource:[NSString stringWithUTF8String:JoltImageShader] options:nil error:&error];
|
|
77
|
+
MTLRenderPipelineDescriptor *descriptor=[MTLRenderPipelineDescriptor new];
|
|
78
|
+
descriptor.vertexFunction=[library newFunctionWithName:@"joltVertex"];
|
|
79
|
+
descriptor.fragmentFunction=[library newFunctionWithName:@"joltImage"];
|
|
80
|
+
descriptor.colorAttachments[0].pixelFormat=MTLPixelFormatBGRA8Unorm;
|
|
81
|
+
if(library) cached=[device newRenderPipelineStateWithDescriptor:descriptor error:&error];
|
|
82
|
+
if(error) NSLog(@"Jolt image shader unavailable: %@",error.localizedDescription);
|
|
83
|
+
});
|
|
84
|
+
if(!cached) return nil;
|
|
85
|
+
JoltImageEffect *effect=[JoltImageEffect new];
|
|
86
|
+
effect.image=view; effect.original=view.image; effect.originalInteraction=view.userInteractionEnabled;
|
|
87
|
+
effect.listener=listener; effect.pipeline=cached; effect.queue=[device newCommandQueue];
|
|
88
|
+
effect.duration=duration/1000.; effect.goal=1; effect.paused=YES;
|
|
89
|
+
effect.params=(JoltImageParams){ {(float)w,(float)h}, {(float)(x*scale),(float)(y*scale)}, {0,0}, 0,
|
|
90
|
+
(float)(amplitude*scale),(float)(radius*scale),(float)seed,(float)mode };
|
|
91
|
+
effect.sourceTexture=[effect texture:view.image device:device width:w height:h];
|
|
92
|
+
effect.destinationTexture=mode==2?[effect texture:destination device:device width:w height:h]:effect.sourceTexture;
|
|
93
|
+
if(!effect.sourceTexture || !effect.destinationTexture) return nil;
|
|
94
|
+
effect.textureBytes=w*h*4*(mode==2?2:1);
|
|
95
|
+
MTKView *surface=[[MTKView alloc] initWithFrame:view.bounds device:device];
|
|
96
|
+
surface.opaque=NO; surface.backgroundColor=UIColor.clearColor;
|
|
97
|
+
surface.clearColor=MTLClearColorMake(0,0,0,0);
|
|
98
|
+
surface.userInteractionEnabled=NO; surface.accessibilityElementsHidden=YES;
|
|
99
|
+
surface.colorPixelFormat=MTLPixelFormatBGRA8Unorm;
|
|
100
|
+
surface.autoResizeDrawable=NO; surface.drawableSize=CGSizeMake(w,h);
|
|
101
|
+
surface.preferredFramesPerSecond=MIN(120,view.window.screen.maximumFramesPerSecond);
|
|
102
|
+
surface.paused=YES; surface.enableSetNeedsDisplay=YES;
|
|
103
|
+
surface.delegate=effect; effect.surface=surface;
|
|
104
|
+
[view addSubview:surface];
|
|
105
|
+
// Draw the identity frame before suppressing the original raster.
|
|
106
|
+
[surface draw]; view.image=nil;
|
|
107
|
+
if(mode==1) {
|
|
108
|
+
effect.pan=[[UIPanGestureRecognizer alloc] initWithTarget:effect action:@selector(drag:)];
|
|
109
|
+
effect.pan.delegate=effect; effect.pan.cancelsTouchesInView=NO;
|
|
110
|
+
view.userInteractionEnabled=YES; [view addGestureRecognizer:effect.pan];
|
|
111
|
+
}
|
|
112
|
+
return effect;
|
|
113
|
+
}
|
|
114
|
+
- (id<MTLTexture>)texture:(UIImage *)image device:(id<MTLDevice>)device width:(NSUInteger)w height:(NSUInteger)h {
|
|
115
|
+
if(!image.CGImage) return nil;
|
|
116
|
+
NSMutableData *data=[NSMutableData dataWithLength:w*h*4];
|
|
117
|
+
CGColorSpaceRef space=CGColorSpaceCreateDeviceRGB();
|
|
118
|
+
CGContextRef context=CGBitmapContextCreate(data.mutableBytes,w,h,8,w*4,space,kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big);
|
|
119
|
+
CGColorSpaceRelease(space);
|
|
120
|
+
if(!context) return nil;
|
|
121
|
+
CGContextTranslateCTM(context,0,h); CGContextScaleCTM(context,1,-1);
|
|
122
|
+
CGFloat iw=image.size.width,ih=image.size.height;
|
|
123
|
+
UIViewContentMode mode=self.image.contentMode;
|
|
124
|
+
CGFloat factor=mode==UIViewContentModeScaleAspectFill?MAX(w/iw,h/ih):MIN(w/iw,h/ih);
|
|
125
|
+
BOOL unscaled=mode==UIViewContentModeCenter || mode==UIViewContentModeTopLeft;
|
|
126
|
+
CGFloat dw=mode==UIViewContentModeScaleToFill?w:unscaled?iw*self.image.window.screen.scale:iw*factor;
|
|
127
|
+
CGFloat dh=mode==UIViewContentModeScaleToFill?h:unscaled?ih*self.image.window.screen.scale:ih*factor;
|
|
128
|
+
CGFloat left=mode==UIViewContentModeTopLeft?0:(w-dw)/2,top=mode==UIViewContentModeTopLeft?0:(h-dh)/2;
|
|
129
|
+
UIGraphicsPushContext(context); [image drawInRect:CGRectMake(left,top,dw,dh)]; UIGraphicsPopContext();
|
|
130
|
+
CGContextRelease(context);
|
|
131
|
+
MTLTextureDescriptor *descriptor=[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm width:w height:h mipmapped:NO];
|
|
132
|
+
descriptor.usage=MTLTextureUsageShaderRead;
|
|
133
|
+
id<MTLTexture> texture=[device newTextureWithDescriptor:descriptor];
|
|
134
|
+
[texture replaceRegion:MTLRegionMake2D(0,0,w,h) mipmapLevel:0 withBytes:data.bytes bytesPerRow:w*4];
|
|
135
|
+
return texture;
|
|
136
|
+
}
|
|
137
|
+
- (void)play:(double)endpoint {
|
|
138
|
+
if(self.disposed || self.params.mode==1) return;
|
|
139
|
+
self.goal=endpoint; self.startProgress=self.progress; self.remaining=MAX(.001,self.duration*fabs(endpoint-self.progress));
|
|
140
|
+
self.anchor=CACurrentMediaTime(); self.playing=YES; self.settling=NO; self.paused=NO;
|
|
141
|
+
self.previousFrame=0; self.surface.enableSetNeedsDisplay=NO; self.surface.paused=NO;
|
|
142
|
+
}
|
|
143
|
+
- (void)pause {
|
|
144
|
+
if(self.disposed) return;
|
|
145
|
+
[self advance:CACurrentMediaTime()]; self.paused=YES;
|
|
146
|
+
self.surface.paused=YES; self.surface.enableSetNeedsDisplay=YES;
|
|
147
|
+
}
|
|
148
|
+
- (void)resume {
|
|
149
|
+
if(self.disposed || !self.paused) return;
|
|
150
|
+
if(self.params.mode==1) {
|
|
151
|
+
self.paused=NO;
|
|
152
|
+
if(self.settling && self.playing) {
|
|
153
|
+
self.settleStart=self.params.displacement;self.anchor=CACurrentMediaTime();
|
|
154
|
+
self.remaining=MIN(.6,self.duration);self.previousFrame=0;
|
|
155
|
+
self.surface.enableSetNeedsDisplay=NO;self.surface.paused=NO;
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
[self play:self.goal];
|
|
160
|
+
}
|
|
161
|
+
- (void)seek:(double)value {
|
|
162
|
+
if(self.disposed || self.params.mode==1) return;
|
|
163
|
+
BOOL hold=self.paused; self.progress=value;
|
|
164
|
+
JoltImageParams p=self.params;p.progress=value;self.params=p;
|
|
165
|
+
[self.surface draw];
|
|
166
|
+
if(!hold) [self play:self.goal];
|
|
167
|
+
}
|
|
168
|
+
- (void)finish:(double)endpoint {
|
|
169
|
+
if(self.disposed || self.params.mode==1) return;
|
|
170
|
+
self.progress=endpoint; self.playing=NO;
|
|
171
|
+
self.surface.paused=YES;
|
|
172
|
+
if(self.listener) self.listener(YES,endpoint);
|
|
173
|
+
}
|
|
174
|
+
- (void)stop {
|
|
175
|
+
[self detach:YES];
|
|
176
|
+
}
|
|
177
|
+
- (void)stopPreservingImage {
|
|
178
|
+
[self detach:NO];
|
|
179
|
+
}
|
|
180
|
+
- (void)detach:(BOOL)restoreImage {
|
|
181
|
+
if(self.disposed) return;
|
|
182
|
+
self.disposed=YES; self.surface.paused=YES; self.surface.delegate=nil;
|
|
183
|
+
[self.surface removeFromSuperview]; self.surface=nil;
|
|
184
|
+
if(self.pan) { [self.image removeGestureRecognizer:self.pan];self.image.userInteractionEnabled=self.originalInteraction;self.pan=nil; }
|
|
185
|
+
if(restoreImage && !self.image.image) self.image.image=self.original;
|
|
186
|
+
self.sourceTexture=nil;self.destinationTexture=nil;self.original=nil;self.queue=nil;
|
|
187
|
+
self.textureBytes=0; self.listener=nil;
|
|
188
|
+
}
|
|
189
|
+
- (void)advance:(double)now {
|
|
190
|
+
if(!self.playing || self.paused || self.disposed) return;
|
|
191
|
+
double t=MIN(1,MAX(0,(now-self.anchor)/self.remaining));
|
|
192
|
+
JoltImageParams p=self.params;
|
|
193
|
+
if(self.settling) p.displacement=self.settleStart*(float)(exp(-8*t)*(1-t));
|
|
194
|
+
else {self.progress=self.startProgress+(self.goal-self.startProgress)*t;p.progress=self.progress;}
|
|
195
|
+
self.params=p;
|
|
196
|
+
if(t>=1) {
|
|
197
|
+
self.playing=NO;self.surface.paused=YES;self.surface.enableSetNeedsDisplay=YES;
|
|
198
|
+
if(!self.settling && self.listener) self.listener(YES,self.progress);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
- (void)drawInMTKView:(MTKView *)view {
|
|
202
|
+
if(self.disposed) return;
|
|
203
|
+
double now=CACurrentMediaTime();
|
|
204
|
+
if(self.previousFrame>0 && self.playing) self.longestFrame=MAX(self.longestFrame,now-self.previousFrame);
|
|
205
|
+
self.previousFrame=now;self.frames++;
|
|
206
|
+
[self advance:now];
|
|
207
|
+
if(self.disposed) return;
|
|
208
|
+
MTLRenderPassDescriptor *pass=view.currentRenderPassDescriptor;
|
|
209
|
+
id<CAMetalDrawable> drawable=view.currentDrawable;
|
|
210
|
+
if(!pass || !drawable) return;
|
|
211
|
+
id<MTLCommandBuffer> command=[self.queue commandBuffer];
|
|
212
|
+
id<MTLRenderCommandEncoder> encoder=[command renderCommandEncoderWithDescriptor:pass];
|
|
213
|
+
[encoder setRenderPipelineState:self.pipeline];
|
|
214
|
+
JoltImageParams params=self.params;
|
|
215
|
+
[encoder setFragmentBytes:¶ms length:sizeof(params) atIndex:0];
|
|
216
|
+
[encoder setFragmentTexture:self.sourceTexture atIndex:0];
|
|
217
|
+
[encoder setFragmentTexture:self.destinationTexture atIndex:1];
|
|
218
|
+
[encoder drawPrimitives:MTLPrimitiveTypeTriangleStrip vertexStart:0 vertexCount:4];
|
|
219
|
+
[encoder endEncoding]; [command presentDrawable:drawable]; [command commit];
|
|
220
|
+
}
|
|
221
|
+
- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {}
|
|
222
|
+
- (void)drag:(UIPanGestureRecognizer *)gesture {
|
|
223
|
+
if(self.disposed || self.paused) return;
|
|
224
|
+
CGFloat scale=self.image.window.screen.scale;
|
|
225
|
+
JoltImageParams p=self.params;
|
|
226
|
+
if(gesture.state==UIGestureRecognizerStateBegan) {
|
|
227
|
+
self.playing=NO;self.surface.paused=YES;self.surface.enableSetNeedsDisplay=YES;
|
|
228
|
+
CGPoint position=[gesture locationInView:self.image],translation=[gesture translationInView:self.image];
|
|
229
|
+
p.origin=(vector_float2){(float)((position.x-translation.x)*scale),(float)((position.y-translation.y)*scale)};
|
|
230
|
+
}
|
|
231
|
+
if(gesture.state==UIGestureRecognizerStateBegan || gesture.state==UIGestureRecognizerStateChanged) {
|
|
232
|
+
CGPoint delta=[gesture translationInView:self.image];
|
|
233
|
+
double length=hypot(delta.x,delta.y)*scale;
|
|
234
|
+
double factor=length==0?0:p.amplitude*tanh(length/MAX(p.amplitude,1))/length;
|
|
235
|
+
p.displacement=(vector_float2){(float)(delta.x*scale*factor),(float)(delta.y*scale*factor)};
|
|
236
|
+
self.params=p;[self.surface draw];
|
|
237
|
+
} else if(gesture.state==UIGestureRecognizerStateEnded || gesture.state==UIGestureRecognizerStateCancelled || gesture.state==UIGestureRecognizerStateFailed) {
|
|
238
|
+
self.settleStart=p.displacement;self.anchor=CACurrentMediaTime();self.remaining=MIN(.6,self.duration);
|
|
239
|
+
self.playing=YES;self.settling=YES;self.surface.enableSetNeedsDisplay=NO;self.surface.paused=NO;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
@end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
3
|
+
@interface JoltPhotoZoom : NSObject
|
|
4
|
+
+ (instancetype)attachImage:(UIImageView *)image viewport:(UIView *)viewport
|
|
5
|
+
minimum:(double)minimum maximum:(double)maximum doubleTap:(double)doubleTap
|
|
6
|
+
dismiss:(BOOL)dismiss reduced:(BOOL)reduced
|
|
7
|
+
backdrop:(nullable UIView *)backdrop chrome:(nullable UIView *)chrome
|
|
8
|
+
onDismiss:(void (^)(double,double,double,double,double))onDismiss
|
|
9
|
+
onSettled:(void (^)(double,double,double))onSettled;
|
|
10
|
+
@property(nonatomic, readonly) double x;
|
|
11
|
+
@property(nonatomic, readonly) double y;
|
|
12
|
+
@property(nonatomic, readonly) double scale;
|
|
13
|
+
@property(nonatomic, readonly) double dismissProgress;
|
|
14
|
+
@property(nonatomic, readonly) BOOL dismissPending;
|
|
15
|
+
- (void)zoom:(double)scale focusX:(double)x focusY:(double)y animated:(BOOL)animated;
|
|
16
|
+
- (void)reset:(BOOL)animated;
|
|
17
|
+
- (void)requestDismiss;
|
|
18
|
+
- (void)setReduced:(BOOL)reduced;
|
|
19
|
+
- (void)setActive:(BOOL)active;
|
|
20
|
+
- (void)layout;
|
|
21
|
+
- (void)stop;
|
|
22
|
+
@end
|
|
23
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#import "JoltPhotoZoom.h"
|
|
2
|
+
#import <QuartzCore/QuartzCore.h>
|
|
3
|
+
|
|
4
|
+
static double JoltClamp(double v,double lo,double hi){return MAX(lo,MIN(hi,v));}
|
|
5
|
+
static double JoltRubber(double v,double lo,double hi,double size){
|
|
6
|
+
double edge=JoltClamp(v,lo,hi),d=v-edge;
|
|
7
|
+
return edge+copysign(size*(1-1/(fabs(d)*.55/MAX(size,1)+1)),d);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@interface JoltPhotoZoom () <UIGestureRecognizerDelegate>
|
|
11
|
+
@property(nonatomic,weak) UIImageView *image;
|
|
12
|
+
@property(nonatomic,weak) UIView *viewport,*backdrop,*chrome;
|
|
13
|
+
@property(nonatomic,strong) UIPinchGestureRecognizer *pinch;
|
|
14
|
+
@property(nonatomic,strong) UIPanGestureRecognizer *pan;
|
|
15
|
+
@property(nonatomic,strong) UITapGestureRecognizer *tap;
|
|
16
|
+
@property(nonatomic,strong) CADisplayLink *clock;
|
|
17
|
+
@property(nonatomic,copy) void (^onDismiss)(double,double,double,double,double);
|
|
18
|
+
@property(nonatomic,copy) void (^onSettled)(double,double,double);
|
|
19
|
+
@property(nonatomic) CGAffineTransform original;
|
|
20
|
+
@property(nonatomic) BOOL originalInteraction,originalClip,reduced,active,disposed,dismiss,closing;
|
|
21
|
+
@property(nonatomic) double minimum,maximum,doubleTap,backdropAlpha,chromeAlpha;
|
|
22
|
+
@property(nonatomic,readwrite) double x,y,scale,dismissProgress;
|
|
23
|
+
@property(nonatomic,readwrite) BOOL dismissPending;
|
|
24
|
+
@property(nonatomic) CGPoint anchor,lastFocus,lastPan,initialTranslation;
|
|
25
|
+
@property(nonatomic) CGPoint rawPan;
|
|
26
|
+
@property(nonatomic) double pinchFloor;
|
|
27
|
+
@property(nonatomic) CGRect content;
|
|
28
|
+
@property(nonatomic) CGSize viewportSize,imageSize;
|
|
29
|
+
@property(nonatomic) NSInteger mode,touches;
|
|
30
|
+
@property(nonatomic) double vx,vy,startTime,startX,startY,startScale,startProgress,targetX,targetY,targetScale;
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
@implementation JoltPhotoZoom
|
|
34
|
+
+ (instancetype)attachImage:(UIImageView *)image viewport:(UIView *)viewport
|
|
35
|
+
minimum:(double)minimum maximum:(double)maximum doubleTap:(double)doubleTap
|
|
36
|
+
dismiss:(BOOL)dismiss reduced:(BOOL)reduced backdrop:(UIView *)backdrop chrome:(UIView *)chrome
|
|
37
|
+
onDismiss:(void (^)(double,double,double,double,double))onDismiss
|
|
38
|
+
onSettled:(void (^)(double,double,double))onSettled {
|
|
39
|
+
JoltPhotoZoom *z=[JoltPhotoZoom new];
|
|
40
|
+
z.image=image;z.viewport=viewport;z.backdrop=backdrop;z.chrome=chrome;
|
|
41
|
+
z.minimum=minimum;z.maximum=maximum;z.doubleTap=doubleTap;z.dismiss=dismiss;z->_reduced=reduced;z.active=YES;
|
|
42
|
+
z.onDismiss=onDismiss;z.onSettled=onSettled;
|
|
43
|
+
z.original=image.transform;z.originalInteraction=viewport.userInteractionEnabled;z.originalClip=viewport.clipsToBounds;
|
|
44
|
+
z.backdropAlpha=backdrop?backdrop.alpha:1;z.chromeAlpha=chrome?chrome.alpha:1;z.scale=minimum;
|
|
45
|
+
z.pan=[[UIPanGestureRecognizer alloc] initWithTarget:z action:@selector(panned:)];
|
|
46
|
+
z.pinch=[[UIPinchGestureRecognizer alloc] initWithTarget:z action:@selector(pinched:)];
|
|
47
|
+
z.tap=[[UITapGestureRecognizer alloc] initWithTarget:z action:@selector(tapped:)];
|
|
48
|
+
z.tap.numberOfTapsRequired=2;z.pan.delegate=z;z.pinch.delegate=z;z.tap.delegate=z;
|
|
49
|
+
[viewport addGestureRecognizer:z.pan];[viewport addGestureRecognizer:z.pinch];[viewport addGestureRecognizer:z.tap];
|
|
50
|
+
viewport.userInteractionEnabled=YES;viewport.clipsToBounds=YES;
|
|
51
|
+
[z measure];[z apply];
|
|
52
|
+
return z;
|
|
53
|
+
}
|
|
54
|
+
- (void)measure {
|
|
55
|
+
self.viewportSize=self.viewport.bounds.size;self.imageSize=self.image.bounds.size;
|
|
56
|
+
CGSize intrinsic=self.image.image.size;
|
|
57
|
+
double iw=self.imageSize.width,ih=self.imageSize.height,dw=intrinsic.width,dh=intrinsic.height;
|
|
58
|
+
if(dw<=0 || dh<=0){dw=iw;dh=ih;}
|
|
59
|
+
UIViewContentMode fit=self.image.contentMode;
|
|
60
|
+
double factor=fit==UIViewContentModeScaleAspectFill?MAX(iw/dw,ih/dh):MIN(iw/dw,ih/dh);
|
|
61
|
+
if(fit==UIViewContentModeScaleToFill){dw=iw;dh=ih;}
|
|
62
|
+
else if(fit!=UIViewContentModeCenter && fit!=UIViewContentModeTopLeft){dw*=factor;dh*=factor;}
|
|
63
|
+
CGPoint pivot=CGPointMake(self.image.layer.anchorPoint.x*iw,self.image.layer.anchorPoint.y*ih);
|
|
64
|
+
self.anchor=CGPointMake(self.image.layer.position.x+self.original.tx,self.image.layer.position.y+self.original.ty);
|
|
65
|
+
double ox=fit==UIViewContentModeTopLeft?0:(iw-dw)/2,oy=fit==UIViewContentModeTopLeft?0:(ih-dh)/2;
|
|
66
|
+
double left=MAX(0,ox),top=MAX(0,oy),right=MIN(iw,ox+dw),bottom=MIN(ih,oy+dh);
|
|
67
|
+
self.content=CGRectMake((left-pivot.x)*self.original.a,(top-pivot.y)*self.original.d,(right-left)*self.original.a,(bottom-top)*self.original.d);
|
|
68
|
+
}
|
|
69
|
+
- (CGRect)boundsFor:(double)scale {
|
|
70
|
+
double loX=self.viewportSize.width-self.anchor.x-CGRectGetMaxX(self.content)*scale;
|
|
71
|
+
double hiX=-self.anchor.x-CGRectGetMinX(self.content)*scale;
|
|
72
|
+
double loY=self.viewportSize.height-self.anchor.y-CGRectGetMaxY(self.content)*scale;
|
|
73
|
+
double hiY=-self.anchor.y-CGRectGetMinY(self.content)*scale;
|
|
74
|
+
if(loX>hiX)loX=hiX=(loX+hiX)/2;
|
|
75
|
+
if(loY>hiY)loY=hiY=(loY+hiY)/2;
|
|
76
|
+
return CGRectMake(loX,loY,hiX-loX,hiY-loY);
|
|
77
|
+
}
|
|
78
|
+
- (void)apply {
|
|
79
|
+
self.image.transform=CGAffineTransformMake(self.original.a*self.scale,0,0,self.original.d*self.scale,self.original.tx+self.x,self.original.ty+self.y);
|
|
80
|
+
self.backdrop.alpha=self.backdropAlpha*(1-self.dismissProgress);
|
|
81
|
+
self.chrome.alpha=self.chromeAlpha*(1-self.dismissProgress);
|
|
82
|
+
}
|
|
83
|
+
- (void)zoom:(double)value focusX:(double)fx focusY:(double)fy animated:(BOOL)animated {
|
|
84
|
+
if(self.disposed || self.dismissPending)return;
|
|
85
|
+
[self cancelAnimation];double next=JoltClamp(value,self.minimum,self.maximum);
|
|
86
|
+
double x=fx-self.anchor.x-(fx-self.anchor.x-self.x)*next/self.scale;
|
|
87
|
+
double y=fy-self.anchor.y-(fy-self.anchor.y-self.y)*next/self.scale;
|
|
88
|
+
CGRect b=[self boundsFor:next];
|
|
89
|
+
[self springX:JoltClamp(x,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(y,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:next vx:0 vy:0 animated:animated close:NO];
|
|
90
|
+
}
|
|
91
|
+
- (void)reset:(BOOL)animated {
|
|
92
|
+
if(self.disposed)return;
|
|
93
|
+
self.dismissPending=NO;self.mode=0;CGRect b=[self boundsFor:self.minimum];
|
|
94
|
+
[self springX:JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:self.minimum vx:0 vy:0 animated:animated close:NO];
|
|
95
|
+
}
|
|
96
|
+
- (void)requestDismiss {
|
|
97
|
+
if(self.disposed || self.dismissPending || !self.dismiss)return;
|
|
98
|
+
CGRect b=[self boundsFor:self.minimum];
|
|
99
|
+
[self springX:JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b)) y:JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b)) scale:self.minimum vx:0 vy:0 animated:YES close:YES];
|
|
100
|
+
}
|
|
101
|
+
- (void)setReduced:(BOOL)reduced { _reduced=reduced;if(reduced){if(self.dismissPending)[self reset:NO];else [self settle:NO];} }
|
|
102
|
+
- (void)setActive:(BOOL)active {
|
|
103
|
+
_active=active;self.pan.enabled=active;self.pinch.enabled=active;self.tap.enabled=active;
|
|
104
|
+
if(!active){[self cancelAnimation];self.dismissPending=NO;[self reset:NO];}
|
|
105
|
+
}
|
|
106
|
+
- (void)layout {
|
|
107
|
+
if(self.disposed)return;
|
|
108
|
+
if(CGSizeEqualToSize(self.viewportSize,self.viewport.bounds.size) && CGSizeEqualToSize(self.imageSize,self.image.bounds.size))return;
|
|
109
|
+
[self cancelAnimation];self.dismissPending=NO;[self measure];self.vx=0;self.vy=0;self.mode=0;[self settle:NO];
|
|
110
|
+
}
|
|
111
|
+
- (void)stop {
|
|
112
|
+
if(self.disposed)return;
|
|
113
|
+
self.disposed=YES;[self cancelAnimation];
|
|
114
|
+
[self.viewport removeGestureRecognizer:self.pan];[self.viewport removeGestureRecognizer:self.pinch];[self.viewport removeGestureRecognizer:self.tap];
|
|
115
|
+
self.viewport.userInteractionEnabled=self.originalInteraction;self.viewport.clipsToBounds=self.originalClip;
|
|
116
|
+
self.image.transform=self.original;self.backdrop.alpha=self.backdropAlpha;self.chrome.alpha=self.chromeAlpha;
|
|
117
|
+
self.onDismiss=nil;self.onSettled=nil;
|
|
118
|
+
}
|
|
119
|
+
- (void)cancelAnimation { [self.clock invalidate];self.clock=nil;self.closing=NO; }
|
|
120
|
+
- (void)springX:(double)x y:(double)y scale:(double)scale vx:(double)vx vy:(double)vy animated:(BOOL)animated close:(BOOL)close {
|
|
121
|
+
[self cancelAnimation];
|
|
122
|
+
if(!animated || self.reduced){
|
|
123
|
+
self.x=x;self.y=y;self.scale=scale;self.dismissProgress=0;[self apply];
|
|
124
|
+
if(close)[self emitDismiss:0];else if(self.onSettled)self.onSettled(x,y,scale);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
self.startX=self.x;self.startY=self.y;self.startScale=self.scale;self.startProgress=self.dismissProgress;
|
|
128
|
+
self.targetX=x;self.targetY=y;self.targetScale=scale;self.vx=vx;self.vy=vy;self.closing=close;
|
|
129
|
+
self.startTime=CACurrentMediaTime();self.clock=[CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
|
|
130
|
+
self.clock.preferredFramesPerSecond=MIN(120,self.viewport.window.screen.maximumFramesPerSecond);
|
|
131
|
+
[self.clock addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
132
|
+
}
|
|
133
|
+
- (void)tick:(CADisplayLink *)clock {
|
|
134
|
+
double t=MIN(.55,MAX(0,CACurrentMediaTime()-self.startTime)),decay=exp(-16*t);
|
|
135
|
+
self.x=self.targetX+((self.startX-self.targetX)+(self.vx+16*(self.startX-self.targetX))*t)*decay;
|
|
136
|
+
self.y=self.targetY+((self.startY-self.targetY)+(self.vy+16*(self.startY-self.targetY))*t)*decay;
|
|
137
|
+
self.scale=self.targetScale+(self.startScale-self.targetScale)*(1+16*t)*decay;
|
|
138
|
+
self.dismissProgress=self.startProgress*(1+16*t)*decay;
|
|
139
|
+
if(t>=.55){self.x=self.targetX;self.y=self.targetY;self.scale=self.targetScale;self.dismissProgress=0;}
|
|
140
|
+
[self apply];
|
|
141
|
+
if(t>=.55){BOOL close=self.closing;[self cancelAnimation];if(close)[self emitDismiss:0];else if(self.onSettled)self.onSettled(self.x,self.y,self.scale);}
|
|
142
|
+
}
|
|
143
|
+
- (void)settle:(BOOL)animated {
|
|
144
|
+
if(self.disposed)return;
|
|
145
|
+
double s=JoltClamp(self.scale,self.minimum,self.maximum);CGRect b=[self boundsFor:s];
|
|
146
|
+
double x=JoltClamp(self.x+self.vx*.12,CGRectGetMinX(b),CGRectGetMaxX(b));
|
|
147
|
+
double y=JoltClamp(self.y+self.vy*.12,CGRectGetMinY(b),CGRectGetMaxY(b));
|
|
148
|
+
if(self.mode==2){s=self.minimum;b=[self boundsFor:s];x=JoltClamp(0,CGRectGetMinX(b),CGRectGetMaxX(b));y=JoltClamp(0,CGRectGetMinY(b),CGRectGetMaxY(b));}
|
|
149
|
+
[self springX:x y:y scale:s vx:self.vx vy:self.vy animated:animated close:NO];
|
|
150
|
+
}
|
|
151
|
+
- (void)emitDismiss:(double)velocity {
|
|
152
|
+
if(self.disposed || self.dismissPending)return;
|
|
153
|
+
self.dismissPending=YES;
|
|
154
|
+
if(self.onDismiss)self.onDismiss(self.x,self.y,self.scale,velocity,self.dismissProgress);
|
|
155
|
+
}
|
|
156
|
+
- (void)pinched:(UIPinchGestureRecognizer *)gesture {
|
|
157
|
+
if(self.disposed || self.dismissPending)return;
|
|
158
|
+
CGPoint focus=[gesture locationInView:self.viewport];
|
|
159
|
+
if(gesture.state==UIGestureRecognizerStateBegan){
|
|
160
|
+
[self cancelAnimation];self.mode=1;self.pinchFloor=MIN(self.minimum,self.scale);self.lastFocus=focus;gesture.scale=1;
|
|
161
|
+
} else if(gesture.state==UIGestureRecognizerStateChanged){
|
|
162
|
+
double next=JoltClamp(self.scale*gesture.scale,self.pinchFloor,self.maximum);
|
|
163
|
+
self.x=focus.x-self.anchor.x-(self.lastFocus.x-self.anchor.x-self.x)*next/self.scale;
|
|
164
|
+
self.y=focus.y-self.anchor.y-(self.lastFocus.y-self.anchor.y-self.y)*next/self.scale;
|
|
165
|
+
self.scale=next;self.lastFocus=focus;gesture.scale=1;
|
|
166
|
+
self.dismissProgress=JoltClamp((self.minimum-self.scale)/(self.minimum*.15),0,1);
|
|
167
|
+
[self rebasePan];[self apply];
|
|
168
|
+
} else if(gesture.state==UIGestureRecognizerStateEnded || gesture.state==UIGestureRecognizerStateCancelled){
|
|
169
|
+
self.lastPan=[self.pan translationInView:self.viewport];self.touches=self.pan.numberOfTouches;
|
|
170
|
+
self.vx=0;self.vy=0;
|
|
171
|
+
if(self.pan.state!=UIGestureRecognizerStateChanged && self.pan.state!=UIGestureRecognizerStateBegan)[self settle:YES];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
- (void)panned:(UIPanGestureRecognizer *)gesture {
|
|
175
|
+
if(self.disposed || self.dismissPending)return;
|
|
176
|
+
CGPoint translation=[gesture translationInView:self.viewport];
|
|
177
|
+
if(gesture.state==UIGestureRecognizerStateBegan){
|
|
178
|
+
[self cancelAnimation];self.vx=0;self.vy=0;self.lastPan=CGPointZero;self.touches=gesture.numberOfTouches;
|
|
179
|
+
[self rebasePan];
|
|
180
|
+
self.mode=self.scale<=self.minimum+.001 && self.pinch.state!=UIGestureRecognizerStateBegan && self.pinch.state!=UIGestureRecognizerStateChanged?2:1;
|
|
181
|
+
}
|
|
182
|
+
if(gesture.state==UIGestureRecognizerStateBegan || gesture.state==UIGestureRecognizerStateChanged){
|
|
183
|
+
if(self.pinch.state==UIGestureRecognizerStateBegan || self.pinch.state==UIGestureRecognizerStateChanged || self.touches!=gesture.numberOfTouches){
|
|
184
|
+
self.lastPan=translation;self.touches=gesture.numberOfTouches;[self rebasePan];return;
|
|
185
|
+
}
|
|
186
|
+
double dx=translation.x-self.lastPan.x,dy=translation.y-self.lastPan.y;
|
|
187
|
+
self.lastPan=translation;
|
|
188
|
+
if(self.mode==2){
|
|
189
|
+
self.x+=dx*.4;self.y+=dy;self.dismissProgress=JoltClamp(fabs(self.y)/MAX(self.viewportSize.height*.5,1),0,1);
|
|
190
|
+
self.scale=self.minimum*(1-self.dismissProgress*.15);
|
|
191
|
+
}else{
|
|
192
|
+
CGRect b=[self boundsFor:self.scale];
|
|
193
|
+
self.rawPan=CGPointMake(self.rawPan.x+dx,self.rawPan.y+dy);
|
|
194
|
+
self.x=JoltRubber(self.rawPan.x,CGRectGetMinX(b),CGRectGetMaxX(b),self.viewportSize.width);
|
|
195
|
+
self.y=JoltRubber(self.rawPan.y,CGRectGetMinY(b),CGRectGetMaxY(b),self.viewportSize.height);
|
|
196
|
+
}
|
|
197
|
+
[self apply];
|
|
198
|
+
}else if(gesture.state==UIGestureRecognizerStateEnded || gesture.state==UIGestureRecognizerStateCancelled || gesture.state==UIGestureRecognizerStateFailed){
|
|
199
|
+
CGPoint velocity=[gesture velocityInView:self.viewport];self.vx=velocity.x;self.vy=velocity.y;
|
|
200
|
+
BOOL commit=gesture.state==UIGestureRecognizerStateEnded && self.mode==2 &&
|
|
201
|
+
(self.dismissProgress>.35 || (fabs(velocity.y)>1000 && velocity.y*self.y>0));
|
|
202
|
+
if(commit)[self emitDismiss:velocity.y];
|
|
203
|
+
else if(self.pinch.state!=UIGestureRecognizerStateChanged && self.pinch.state!=UIGestureRecognizerStateBegan)[self settle:YES];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
- (void)tapped:(UITapGestureRecognizer *)gesture {
|
|
207
|
+
if(gesture.state!=UIGestureRecognizerStateEnded || self.dismissPending)return;
|
|
208
|
+
CGPoint point=[gesture locationInView:self.viewport];
|
|
209
|
+
[self zoom:self.scale>self.minimum+.01?self.minimum:MIN(self.maximum,self.doubleTap) focusX:point.x focusY:point.y animated:YES];
|
|
210
|
+
}
|
|
211
|
+
- (void)rebasePan {
|
|
212
|
+
CGRect b=[self boundsFor:self.scale];
|
|
213
|
+
double ex=JoltClamp(self.x,CGRectGetMinX(b),CGRectGetMaxX(b)),ey=JoltClamp(self.y,CGRectGetMinY(b),CGRectGetMaxY(b));
|
|
214
|
+
double dx=self.x-ex,dy=self.y-ey;
|
|
215
|
+
self.rawPan=CGPointMake(ex+dx/(.55*MAX(.001,1-fabs(dx)/MAX(1,self.viewportSize.width))),
|
|
216
|
+
ey+dy/(.55*MAX(.001,1-fabs(dy)/MAX(1,self.viewportSize.height))));
|
|
217
|
+
}
|
|
218
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gesture {
|
|
219
|
+
if(self.disposed || !self.active || self.dismissPending)return NO;
|
|
220
|
+
if(gesture==self.pan && self.scale<=self.minimum+.001){
|
|
221
|
+
CGPoint velocity=[self.pan velocityInView:self.viewport];
|
|
222
|
+
return self.dismiss && fabs(velocity.y)>fabs(velocity.x)*1.2;
|
|
223
|
+
}
|
|
224
|
+
return YES;
|
|
225
|
+
}
|
|
226
|
+
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)one shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)other {
|
|
227
|
+
return (one==self.pan && other==self.pinch)||(one==self.pinch && other==self.pan);
|
|
228
|
+
}
|
|
229
|
+
@end
|
package/src/android-plugin.d.ts
CHANGED
|
@@ -1,4 +1,82 @@
|
|
|
1
1
|
declare namespace org.rafaelaugusto.jolt {
|
|
2
|
+
class PhotoZoom {
|
|
3
|
+
constructor(
|
|
4
|
+
image: android.widget.ImageView,
|
|
5
|
+
viewport: android.view.View,
|
|
6
|
+
bitmapWidth: number,
|
|
7
|
+
bitmapHeight: number,
|
|
8
|
+
minimum: number,
|
|
9
|
+
maximum: number,
|
|
10
|
+
doubleTap: number,
|
|
11
|
+
dismiss: boolean,
|
|
12
|
+
reduced: boolean,
|
|
13
|
+
backdrop: android.view.View | null,
|
|
14
|
+
chrome: android.view.View | null,
|
|
15
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
16
|
+
previousImageTouch: android.view.View.OnTouchListener | null,
|
|
17
|
+
listener: PhotoZoom.Listener,
|
|
18
|
+
)
|
|
19
|
+
getX(): number
|
|
20
|
+
getY(): number
|
|
21
|
+
getScale(): number
|
|
22
|
+
getDismissProgress(): number
|
|
23
|
+
isDismissPending(): boolean
|
|
24
|
+
zoom(scale: number, x: number, y: number, animated: boolean): void
|
|
25
|
+
reset(animated: boolean): void
|
|
26
|
+
requestDismiss(): void
|
|
27
|
+
setReduced(value: boolean): void
|
|
28
|
+
setActive(value: boolean): void
|
|
29
|
+
stop(): void
|
|
30
|
+
}
|
|
31
|
+
namespace PhotoZoom {
|
|
32
|
+
class Listener {
|
|
33
|
+
constructor(implementation: {
|
|
34
|
+
onDismiss(
|
|
35
|
+
x: number,
|
|
36
|
+
y: number,
|
|
37
|
+
scale: number,
|
|
38
|
+
velocity: number,
|
|
39
|
+
progress: number,
|
|
40
|
+
): void
|
|
41
|
+
onSettled(x: number, y: number, scale: number): void
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class ImageEffect {
|
|
46
|
+
constructor(
|
|
47
|
+
image: android.widget.ImageView,
|
|
48
|
+
source: android.graphics.Bitmap | null,
|
|
49
|
+
destination: android.graphics.Bitmap | null,
|
|
50
|
+
geometry: androidNative.Array<number>,
|
|
51
|
+
mode: number,
|
|
52
|
+
x: number,
|
|
53
|
+
y: number,
|
|
54
|
+
amplitude: number,
|
|
55
|
+
radius: number,
|
|
56
|
+
duration: number,
|
|
57
|
+
seed: number,
|
|
58
|
+
previousTouch: android.view.View.OnTouchListener | null,
|
|
59
|
+
listener: ImageEffect.Listener,
|
|
60
|
+
)
|
|
61
|
+
isSupported(): boolean
|
|
62
|
+
getFailureReason(): string
|
|
63
|
+
getProgress(): number
|
|
64
|
+
getFrames(): number
|
|
65
|
+
getTextureBytes(): number
|
|
66
|
+
getLongestFrameNanos(): number
|
|
67
|
+
play(endpoint: number): void
|
|
68
|
+
pause(): void
|
|
69
|
+
resume(): void
|
|
70
|
+
seek(progress: number): void
|
|
71
|
+
finish(endpoint: number): void
|
|
72
|
+
stop(): void
|
|
73
|
+
stopPreservingImage(): void
|
|
74
|
+
}
|
|
75
|
+
namespace ImageEffect {
|
|
76
|
+
class Listener {
|
|
77
|
+
constructor(implementation: { onEnd(completed: boolean, progress: number): void })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
2
80
|
class KeyboardFollower {
|
|
3
81
|
constructor(
|
|
4
82
|
view: android.view.View,
|