-
After using importStateHistory, the fontFamily and fontScale of the text type will revert to its original state. |
Beta Was this translation helpful? Give feedback.
Answered by
hm21
Jun 15, 2024
Replies: 2 comments 7 replies
-
Ah, okay, I'll take a look at that, but it sounds like it's an issue. |
Beta Was this translation helpful? Give feedback.
5 replies
-
It might be because I set a size for the canvas? const Size backgroundCanvasSize = Size(2480.0, 3508.0);
const Color backgroundCanvasColor = Colors.white;
double width = backgroundCanvasSize.width;
double height = backgroundCanvasSize.height;
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()));
final paint = Paint()..color = backgroundCanvasColor;
canvas.drawRect(Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()), paint);
final picture = recorder.endRecording();
final img = await picture.toImage(width.toInt(), height.toInt());
final pngBytes = await img.toByteData(format: ui.ImageByteFormat.png);
final transparentBytes = pngBytes!.buffer.asUint8List(); Here is the complete example code that can reproduce it: // Dart imports:
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
// Flutter imports:
import 'package:example/pages/pick_image_example.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:image_picker/image_picker.dart';
// Package imports:
import 'package:pro_image_editor/pro_image_editor.dart';
class MoveableBackgroundImageExample extends StatefulWidget {
const MoveableBackgroundImageExample({super.key});
@override
State<MoveableBackgroundImageExample> createState() => _MoveableBackgroundImageExampleState();
}
class _MoveableBackgroundImageExampleState extends State<MoveableBackgroundImageExample> {
final _editorKey = GlobalKey<ProImageEditorState>();
late final StreamController<bool> _openEditorStreamCtrl;
late final StreamController _updateUIStreamCtrl;
late final ScrollController _bottomBarScrollCtrl;
late Uint8List _transparentBytes;
/// Better sense of scale when we start with a large number
final double _initScale = 20;
final _bottomTextStyle = const TextStyle(fontSize: 10.0, color: Colors.white);
final Size _backgroundCanvasSize = const Size(1000, 1000);
final Color _backgroundCanvasColor = Colors.white;
@override
void initState() {
_openEditorStreamCtrl = StreamController.broadcast();
_updateUIStreamCtrl = StreamController.broadcast();
_bottomBarScrollCtrl = ScrollController();
super.initState();
}
@override
void dispose() {
_openEditorStreamCtrl.close();
_updateUIStreamCtrl.close();
_bottomBarScrollCtrl.dispose();
super.dispose();
}
void _openPicker(ImageSource source) async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(source: source);
if (image == null) return;
Uint8List? bytes;
bytes = await image.readAsBytes();
if (!mounted) return;
await precacheImage(MemoryImage(bytes), context);
var decodedImage = await decodeImageFromList(bytes);
if (!mounted) return;
if (kIsWeb || (!Platform.isWindows && !Platform.isLinux && !Platform.isMacOS)) {
Navigator.pop(context);
}
_editorKey.currentState?.addLayer(
StickerLayerData(
offset: Offset.zero,
scale: _initScale * 0.5,
sticker: Image.memory(
bytes,
width: decodedImage.width.toDouble(),
height: decodedImage.height.toDouble(),
fit: BoxFit.cover,
),
),
);
setState(() {});
}
void _chooseCameraOrGallery() async {
/// Open directly the gallery if the camera is not supported
if (!kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
_openPicker(ImageSource.gallery);
return;
}
if (!kIsWeb && Platform.isIOS) {
showCupertinoModalPopup(
context: context,
builder: (BuildContext context) => CupertinoTheme(
data: const CupertinoThemeData(),
child: CupertinoActionSheet(
actions: <CupertinoActionSheetAction>[
CupertinoActionSheetAction(
onPressed: () => _openPicker(ImageSource.camera),
child: const Wrap(
spacing: 7,
runAlignment: WrapAlignment.center,
children: [
Icon(CupertinoIcons.photo_camera),
Text('Camera'),
],
),
),
CupertinoActionSheetAction(
onPressed: () => _openPicker(ImageSource.gallery),
child: const Wrap(
spacing: 7,
runAlignment: WrapAlignment.center,
children: [
Icon(CupertinoIcons.photo),
Text('Gallery'),
],
),
),
],
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
child: const Text('Cancel'),
),
),
),
);
} else {
showModalBottomSheet(
context: context,
showDragHandle: true,
constraints: BoxConstraints(
minWidth: min(MediaQuery.of(context).size.width, 360),
),
builder: (context) {
return Material(
color: Colors.transparent,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.only(bottom: 24, left: 16, right: 16),
child: Wrap(
spacing: 45,
runSpacing: 30,
crossAxisAlignment: WrapCrossAlignment.center,
runAlignment: WrapAlignment.center,
alignment: WrapAlignment.spaceAround,
children: [
MaterialIconActionButton(
primaryColor: const Color(0xFFEC407A),
secondaryColor: const Color(0xFFD3396D),
icon: Icons.photo_camera,
text: 'Camera',
onTap: () => _openPicker(ImageSource.camera),
),
MaterialIconActionButton(
primaryColor: const Color(0xFFBF59CF),
secondaryColor: const Color(0xFFAC44CF),
icon: Icons.image,
text: 'Gallery',
onTap: () => _openPicker(ImageSource.gallery),
),
],
),
),
),
);
},
);
}
}
Future<void> _createBackgroundCanvas() async {
double width = _backgroundCanvasSize.width;
double height = _backgroundCanvasSize.height;
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder, Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()));
final paint = Paint()..color = _backgroundCanvasColor;
canvas.drawRect(Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()), paint);
final picture = recorder.endRecording();
final img = await picture.toImage(width.toInt(), height.toInt());
final pngBytes = await img.toByteData(format: ui.ImageByteFormat.png);
_transparentBytes = pngBytes!.buffer.asUint8List();
}
Size get _editorSize => Size(
MediaQuery.of(context).size.width - MediaQuery.of(context).padding.horizontal,
MediaQuery.of(context).size.height -
kToolbarHeight -
kBottomNavigationBarHeight -
MediaQuery.of(context).padding.vertical,
);
@override
Widget build(BuildContext context) {
return ListTile(
onTap: () async {
LoadingDialog loading = LoadingDialog();
await loading.show(
context,
configs: const ProImageEditorConfigs(),
theme: ThemeData.dark(),
);
await _createBackgroundCanvas();
if (context.mounted) await loading.hide(context);
if (!context.mounted) return;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _buildEditor(),
),
);
},
leading: const Icon(Icons.pan_tool_alt_outlined),
title: const Text('Movable background image'),
subtitle: const Text('Includes how to add multiple images.'),
trailing: const Icon(Icons.chevron_right),
);
}
Widget _buildEditor() {
return Stack(
children: [
LayoutBuilder(builder: (context, constraints) {
return ProImageEditor.memory(
_transparentBytes,
key: _editorKey,
callbacks: ProImageEditorCallbacks(
onImageEditingComplete: (bytes) async {
print('Done ${bytes.length}');
},
mainEditorCallbacks: MainEditorCallbacks(
onOpenSubEditor: (editor) => _openEditorStreamCtrl.add(true),
onCloseSubEditor: (editor) => _openEditorStreamCtrl.add(false),
onUpdateUI: () {
_updateUIStreamCtrl.add(null);
},
),
),
configs: ProImageEditorConfigs(
designMode: platformDesignMode,
imageGenerationConfigs: ImageGeneratioConfigs(
captureOnlyDrawingBounds: true,
/// Set the pixel ratio manually. You can also set this value higher than the device pixel ratio for higher quality.
customPixelRatio: max(
_editorSize.width / MediaQuery.of(context).size.width,
_editorSize.height / MediaQuery.of(context).size.height,
),
),
/// Crop-Rotate, Filter and Blur editors are not supported
cropRotateEditorConfigs: const CropRotateEditorConfigs(enabled: false),
filterEditorConfigs: const FilterEditorConfigs(enabled: false),
blurEditorConfigs: const BlurEditorConfigs(enabled: false),
customWidgets: ImageEditorCustomWidgets(
bottomNavigationBar: _bottomNavigationBar(constraints),
),
imageEditorTheme: const ImageEditorTheme(
uiOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.black,
),
background: Colors.transparent,
paintingEditor: PaintingEditorTheme(background: Colors.transparent),
/// Optionally remove background
/// cropRotateEditor: CropRotateEditorTheme(background: Colors.transparent),
/// filterEditor: FilterEditorTheme(background: Colors.transparent),
/// blurEditor: BlurEditorTheme(background: Colors.transparent),
),
stickerEditorConfigs: StickerEditorConfigs(
enabled: false,
initWidth: (_editorSize.aspectRatio > _backgroundCanvasSize.aspectRatio
? _editorSize.height
: _editorSize.width) /
_initScale,
buildStickers: (setLayer, scrollController) {
// Optionally your code to pick layers
return const SizedBox();
},
)),
);
}),
_buildImportExportButton(),
],
);
}
_buildImportExportButton() {
return Positioned(
bottom: 2 * kBottomNavigationBarHeight,
left: -8,
child: Container(
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: const BorderRadius.only(
topRight: Radius.circular(100),
bottomRight: Radius.circular(100),
),
),
child: IconButton(
onPressed: () async {
var history = await _editorKey.currentState!.exportStateHistory(
configs: const ExportEditorConfigs(historySpan: ExportHistorySpan.current),
);
String jsonStr = await history.toJson();
_editorKey.currentState!.importStateHistory(ImportStateHistory.fromJson(jsonStr));
},
icon: const Icon(
Icons.import_export_outlined,
color: Colors.white,
),
),
),
);
}
Widget _bottomNavigationBar(BoxConstraints constraints) {
return StreamBuilder(
stream: _updateUIStreamCtrl.stream,
builder: (_, __) {
return Scrollbar(
controller: _bottomBarScrollCtrl,
scrollbarOrientation: ScrollbarOrientation.top,
thickness: isDesktop ? null : 0,
child: BottomAppBar(
/// kBottomNavigationBarHeight is important that helperlines will work
height: kBottomNavigationBarHeight,
color: Colors.black,
padding: EdgeInsets.zero,
child: Center(
child: SingleChildScrollView(
controller: _bottomBarScrollCtrl,
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: min(constraints.maxWidth, 500),
maxWidth: 500,
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatIconTextButton(
label: Text('Add Image', style: _bottomTextStyle),
icon: const Icon(
Icons.image_outlined,
size: 22.0,
color: Colors.white,
),
onPressed: _chooseCameraOrGallery,
),
FlatIconTextButton(
label: Text('Paint', style: _bottomTextStyle),
icon: const Icon(
Icons.edit_rounded,
size: 22.0,
color: Colors.white,
),
onPressed: _editorKey.currentState?.openPaintingEditor,
),
FlatIconTextButton(
label: Text('Text', style: _bottomTextStyle),
icon: const Icon(
Icons.text_fields,
size: 22.0,
color: Colors.white,
),
onPressed: _editorKey.currentState?.openTextEditor,
),
FlatIconTextButton(
label: Text('Emoji', style: _bottomTextStyle),
icon: const Icon(
Icons.sentiment_satisfied_alt_rounded,
size: 22.0,
color: Colors.white,
),
onPressed: _editorKey.currentState?.openEmojiEditor,
),
],
),
),
),
),
),
),
);
},
);
}
} |
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing the code with me, it helped me a lot to resolve this issue. I have now released version
3.0.14
which should fix it.For it to work, you need to remove the
fit
value that you add to the image (it is not needed because we set the exact width and height anyway). Below is the code I mean.Please let me know if you still have…