perf: efekt suruklemesinde kitlenme fix (kare hizi throttle, repaint izolasyonu, downsampling, cache sinirlari), .deb guncellendi
This commit is contained in:
@@ -63,6 +63,9 @@ class ImageManager extends ChangeNotifier {
|
||||
.indexWhere((p) => p.split(Platform.pathSeparator).last == base);
|
||||
}
|
||||
if (_currentIndex < 0) _currentIndex = 0;
|
||||
|
||||
// Eski klasörün decode edilmiş görsellerini bırak; canlı görsel korunur
|
||||
PaintingBinding.instance.imageCache.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -86,8 +89,8 @@ class ImageManager extends ChangeNotifier {
|
||||
|
||||
/// Configure image cache for high-res images
|
||||
static void configureCache() {
|
||||
PaintingBinding.instance.imageCache.maximumSize = 1000;
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 1024 * 1024 * 1024; // 1 GB
|
||||
PaintingBinding.instance.imageCache.maximumSize = 16;
|
||||
PaintingBinding.instance.imageCache.maximumSizeBytes = 256 * 1024 * 1024; // 256 MB
|
||||
}
|
||||
|
||||
void clear() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ImageCanvas extends StatefulWidget {
|
||||
@@ -37,6 +38,19 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
bool _ctrlPressed = false;
|
||||
bool _shiftPressed = false;
|
||||
|
||||
// ── Kare hızına eşitlenmiş render tick ──
|
||||
final ValueNotifier<int> _renderTick = ValueNotifier(0);
|
||||
bool _frameScheduled = false;
|
||||
|
||||
void _markRenderDirty() {
|
||||
if (_frameScheduled) return;
|
||||
_frameScheduled = true;
|
||||
SchedulerBinding.instance.scheduleFrameCallback((_) {
|
||||
_frameScheduled = false;
|
||||
_renderTick.value++;
|
||||
});
|
||||
}
|
||||
|
||||
void _clamp() {
|
||||
if (_vpSize.width <= 0 || _vpSize.height <= 0) return;
|
||||
if (_angle != 0) return; // rotate varsa serbest pan
|
||||
@@ -107,6 +121,7 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
@override
|
||||
void dispose() {
|
||||
HardwareKeyboard.instance.removeHandler(_onHardwareKey);
|
||||
_renderTick.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -139,17 +154,15 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
}
|
||||
|
||||
void _resetView() {
|
||||
setState(() {
|
||||
_tx = 0; _ty = 0; _sc = 1.0; _isFilled = true; _vpSize = Size.zero;
|
||||
_angle = 0; _contrast = 1.0; _saturation = 1.0; _brightness = 0;
|
||||
});
|
||||
_tx = 0; _ty = 0; _sc = 1.0; _isFilled = true; _vpSize = Size.zero;
|
||||
_angle = 0; _contrast = 1.0; _saturation = 1.0; _brightness = 0;
|
||||
_markRenderDirty();
|
||||
}
|
||||
|
||||
void _toggleFillFit() {
|
||||
setState(() {
|
||||
_isFilled = !_isFilled; _tx = 0; _ty = 0; _sc = 1.0; _vpSize = Size.zero;
|
||||
_angle = 0; _contrast = 1.0; _saturation = 1.0; _brightness = 0;
|
||||
});
|
||||
_isFilled = !_isFilled; _tx = 0; _ty = 0; _sc = 1.0; _vpSize = Size.zero;
|
||||
_angle = 0; _contrast = 1.0; _saturation = 1.0; _brightness = 0;
|
||||
_markRenderDirty();
|
||||
}
|
||||
|
||||
void _handleDoubleTap() => _toggleFillFit();
|
||||
@@ -175,14 +188,14 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
}
|
||||
_sc = newSc;
|
||||
_clamp();
|
||||
setState(() {});
|
||||
_markRenderDirty();
|
||||
}
|
||||
|
||||
void _onPanUpdate(DragUpdateDetails d) {
|
||||
_tx += d.delta.dx;
|
||||
_ty += d.delta.dy;
|
||||
_clamp();
|
||||
setState(() {});
|
||||
_markRenderDirty();
|
||||
}
|
||||
|
||||
void _onPointerDown(PointerDownEvent e) {
|
||||
@@ -194,13 +207,12 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
if (_lastRightTapTime != null &&
|
||||
now.difference(_lastRightTapTime!) < const Duration(milliseconds: 400) &&
|
||||
(e.localPosition - _lastRightTapPos).distance < 20) {
|
||||
setState(() {
|
||||
_angle = 0;
|
||||
_contrast = 1.0;
|
||||
_saturation = 1.0;
|
||||
_brightness = 0;
|
||||
_interactionMode = 'none';
|
||||
});
|
||||
_angle = 0;
|
||||
_contrast = 1.0;
|
||||
_saturation = 1.0;
|
||||
_brightness = 0;
|
||||
_interactionMode = 'none';
|
||||
_markRenderDirty();
|
||||
_lastRightTapTime = null;
|
||||
return;
|
||||
}
|
||||
@@ -229,26 +241,19 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
|
||||
switch (_interactionMode) {
|
||||
case 'contrast':
|
||||
setState(() {
|
||||
_contrast = (_startContrast + dy * 0.005).clamp(0.0, 2.0);
|
||||
});
|
||||
_contrast = (_startContrast + dy * 0.005).clamp(0.0, 2.0);
|
||||
break;
|
||||
case 'rotate':
|
||||
setState(() {
|
||||
_angle = _startAngle + dy * 0.9;
|
||||
});
|
||||
_angle = _startAngle + dy * 0.9;
|
||||
break;
|
||||
case 'saturation':
|
||||
setState(() {
|
||||
_saturation = (_startSaturation + dy * 0.005).clamp(0.0, 2.0);
|
||||
});
|
||||
_saturation = (_startSaturation + dy * 0.005).clamp(0.0, 2.0);
|
||||
break;
|
||||
case 'brightness':
|
||||
setState(() {
|
||||
_brightness = (_startBrightness + dy * 0.5).clamp(-100.0, 100.0);
|
||||
});
|
||||
_brightness = (_startBrightness + dy * 0.5).clamp(-100.0, 100.0);
|
||||
break;
|
||||
}
|
||||
_markRenderDirty();
|
||||
}
|
||||
|
||||
void _onPointerUp(PointerUpEvent e) {
|
||||
@@ -334,9 +339,15 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
}
|
||||
|
||||
Widget _buildImage() {
|
||||
return Image.file(
|
||||
final longest = _vpSize.longestSide;
|
||||
final cacheWidth = longest > 0 ? (longest * 2).round() : null;
|
||||
final ImageProvider<Object> provider = cacheWidth != null
|
||||
? ResizeImage.resizeIfNeeded(
|
||||
cacheWidth, null, FileImage(File(widget.filePath)))
|
||||
: FileImage(File(widget.filePath));
|
||||
return Image(
|
||||
key: _imageKey,
|
||||
File(widget.filePath),
|
||||
image: provider,
|
||||
fit: _isFilled ? BoxFit.cover : BoxFit.contain,
|
||||
filterQuality: FilterQuality.high,
|
||||
errorBuilder: (_, _, _) => _errorWidget(),
|
||||
@@ -349,65 +360,73 @@ class _ImageCanvasState extends State<ImageCanvas> {
|
||||
builder: (context, constraints) {
|
||||
_vpSize = constraints.biggest;
|
||||
_clamp();
|
||||
|
||||
final vpCenterX = _vpSize.width / 2;
|
||||
final vpCenterY = _vpSize.height / 2;
|
||||
final angleRad = _angle * math.pi / 180;
|
||||
|
||||
Widget imageContent = Builder(
|
||||
builder: (context) {
|
||||
final hasFilter = _saturation != 1.0 || _contrast != 1.0 || _brightness != 0.0;
|
||||
final image = _isFilled
|
||||
? SizedBox.expand(child: _buildImage())
|
||||
: Center(child: _buildImage());
|
||||
return hasFilter
|
||||
? ColorFiltered(colorFilter: _buildCombinedFilter(), child: image)
|
||||
: image;
|
||||
},
|
||||
);
|
||||
|
||||
if (_angle != 0) {
|
||||
imageContent = Transform(
|
||||
alignment: Alignment.topLeft,
|
||||
transform: Matrix4.identity()
|
||||
..translate(vpCenterX, vpCenterY)
|
||||
..rotateZ(angleRad)
|
||||
..translate(-vpCenterX, -vpCenterY),
|
||||
child: imageContent,
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox.expand(
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRect(
|
||||
child: Listener(
|
||||
onPointerDown: _onPointerDown,
|
||||
onPointerMove: _onPointerMove,
|
||||
onPointerUp: _onPointerUp,
|
||||
onPointerSignal: (event) {
|
||||
if (event is PointerScrollEvent) _handleScroll(event);
|
||||
},
|
||||
child: GestureDetector(
|
||||
onDoubleTap: _handleDoubleTap,
|
||||
onPanUpdate: _onPanUpdate,
|
||||
child: Transform(
|
||||
alignment: Alignment.topLeft,
|
||||
transform: Matrix4.identity()
|
||||
..translate(_tx, _ty)
|
||||
..scale(_sc),
|
||||
child: imageContent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
return RepaintBoundary(
|
||||
child: ValueListenableBuilder<int>(
|
||||
valueListenable: _renderTick,
|
||||
builder: (context, _, _) => _buildRenderLayer(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRenderLayer() {
|
||||
final vpCenterX = _vpSize.width / 2;
|
||||
final vpCenterY = _vpSize.height / 2;
|
||||
final angleRad = _angle * math.pi / 180;
|
||||
|
||||
Widget imageContent = Builder(
|
||||
builder: (context) {
|
||||
final hasFilter = _saturation != 1.0 || _contrast != 1.0 || _brightness != 0.0;
|
||||
final image = _isFilled
|
||||
? SizedBox.expand(child: _buildImage())
|
||||
: Center(child: _buildImage());
|
||||
return hasFilter
|
||||
? ColorFiltered(colorFilter: _buildCombinedFilter(), child: image)
|
||||
: image;
|
||||
},
|
||||
);
|
||||
|
||||
if (_angle != 0) {
|
||||
imageContent = Transform(
|
||||
alignment: Alignment.topLeft,
|
||||
transform: Matrix4.identity()
|
||||
..translate(vpCenterX, vpCenterY)
|
||||
..rotateZ(angleRad)
|
||||
..translate(-vpCenterX, -vpCenterY),
|
||||
child: imageContent,
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox.expand(
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRect(
|
||||
child: Listener(
|
||||
onPointerDown: _onPointerDown,
|
||||
onPointerMove: _onPointerMove,
|
||||
onPointerUp: _onPointerUp,
|
||||
onPointerSignal: (event) {
|
||||
if (event is PointerScrollEvent) _handleScroll(event);
|
||||
},
|
||||
child: GestureDetector(
|
||||
onDoubleTap: _handleDoubleTap,
|
||||
onPanUpdate: _onPanUpdate,
|
||||
child: Transform(
|
||||
alignment: Alignment.topLeft,
|
||||
transform: Matrix4.identity()
|
||||
..translate(_tx, _ty)
|
||||
..scale(_sc),
|
||||
child: imageContent,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _errorWidget() {
|
||||
return const Center(
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user