Skip to content

Commit 5e18eec

Browse files
authored
Merge pull request #53 from bluefireteam/feat/import-image
feat: Adding import from image feature
2 parents 70c0874 + 28bc448 commit 5e18eec

File tree

9 files changed

+198
-46
lines changed

9 files changed

+198
-46
lines changed

packages/mini_sprite_editor/lib/app/view/app.dart

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:mini_sprite_editor/config/config.dart';
66
import 'package:mini_sprite_editor/l10n/arb/app_localizations.dart';
77
import 'package:mini_sprite_editor/library/library.dart';
88
import 'package:mini_sprite_editor/map/map.dart';
9+
import 'package:mini_sprite_editor/services/image_importer.dart';
910
import 'package:mini_sprite_editor/sprite/sprite.dart';
1011
import 'package:mini_sprite_editor/workspace/workspace.dart';
1112

@@ -14,61 +15,72 @@ class App extends StatelessWidget {
1415

1516
@override
1617
Widget build(BuildContext context) {
17-
return MultiBlocProvider(
18+
return MultiRepositoryProvider(
1819
providers: [
19-
BlocProvider<ConfigCubit>(create: (_) => ConfigCubit()),
20-
BlocProvider<WorkspaceCubit>(create: (_) => WorkspaceCubit()),
21-
BlocProvider<SpriteCubit>(create: (context) => SpriteCubit()),
22-
BlocProvider<LibraryCubit>(create: (context) => LibraryCubit()),
23-
BlocProvider<MapCubit>(create: (context) => MapCubit()),
20+
RepositoryProvider<ImageImporterService>(
21+
create: (_) => ImageImporterService(),
22+
),
2423
],
25-
child: BlocBuilder<ConfigCubit, ConfigState>(
26-
builder:
27-
(context, state) => MaterialApp(
28-
themeMode: state.themeMode,
29-
theme: ThemeData(),
30-
darkTheme: ThemeData.dark(),
31-
localizationsDelegates: const [
32-
AppLocalizations.delegate,
33-
GlobalMaterialLocalizations.delegate,
34-
],
35-
supportedLocales: AppLocalizations.supportedLocales,
36-
onGenerateRoute: (settings) {
37-
final name = settings.name;
24+
child: MultiBlocProvider(
25+
providers: [
26+
BlocProvider<ConfigCubit>(create: (_) => ConfigCubit()),
27+
BlocProvider<WorkspaceCubit>(create: (_) => WorkspaceCubit()),
28+
BlocProvider<SpriteCubit>(create: (context) => SpriteCubit()),
29+
BlocProvider<LibraryCubit>(create: (context) => LibraryCubit()),
30+
BlocProvider<MapCubit>(create: (context) => MapCubit()),
31+
],
32+
child: BlocBuilder<ConfigCubit, ConfigState>(
33+
builder:
34+
(context, state) => MaterialApp(
35+
themeMode: state.themeMode,
36+
theme: ThemeData(),
37+
darkTheme: ThemeData.dark(),
38+
localizationsDelegates: const [
39+
AppLocalizations.delegate,
40+
GlobalMaterialLocalizations.delegate,
41+
],
42+
supportedLocales: AppLocalizations.supportedLocales,
43+
onGenerateRoute: (settings) {
44+
final name = settings.name;
3845

39-
if (name != null && name != '/') {
40-
final uri = Uri.parse(name);
41-
final colors = uri.queryParameters['colors'];
46+
if (name != null && name != '/') {
47+
final uri = Uri.parse(name);
48+
final colors = uri.queryParameters['colors'];
4249

43-
List<Color>? colorList;
44-
if (colors != null) {
45-
colorList =
46-
colors
47-
.split(',')
48-
.map(int.parse)
49-
.map(Color.new)
50-
.toList();
51-
}
50+
List<Color>? colorList;
51+
if (colors != null) {
52+
colorList =
53+
colors
54+
.split(',')
55+
.map(int.parse)
56+
.map(Color.new)
57+
.toList();
58+
}
5259

53-
final spriteRaw = uri.queryParameters['sprite'];
54-
MiniSprite? sprite;
55-
if (spriteRaw != null) {
56-
try {
57-
sprite = MiniSprite.fromDataString(spriteRaw);
58-
} on Exception catch (_) {
59-
// ignore on invalid sprite data
60+
final spriteRaw = uri.queryParameters['sprite'];
61+
MiniSprite? sprite;
62+
if (spriteRaw != null) {
63+
try {
64+
sprite = MiniSprite.fromDataString(spriteRaw);
65+
} on Exception catch (_) {
66+
// ignore on invalid sprite data
67+
}
6068
}
69+
return MaterialPageRoute(
70+
builder:
71+
(_) => WorkspaceView(
72+
colorList: colorList,
73+
sprite: sprite,
74+
),
75+
);
6176
}
77+
6278
return MaterialPageRoute(
63-
builder:
64-
(_) =>
65-
WorkspaceView(colorList: colorList, sprite: sprite),
79+
builder: (_) => const WorkspaceView(),
6680
);
67-
}
68-
69-
return MaterialPageRoute(builder: (_) => const WorkspaceView());
70-
},
71-
),
81+
},
82+
),
83+
),
7284
),
7385
);
7486
}

packages/mini_sprite_editor/lib/l10n/arb/app_en.arb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,9 @@
223223
"rotateCounterClockwise": "Rotate counter clockwise",
224224
"@rotateCounterClockwise": {
225225
"description": "Label for rotating the sprite counter clockwise"
226+
},
227+
"importFromImage": "Import from image",
228+
"@importFromImage": {
229+
"description": "Import from image label"
226230
}
227231
}

packages/mini_sprite_editor/lib/l10n/arb/app_localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,12 @@ abstract class AppLocalizations {
429429
/// In en, this message translates to:
430430
/// **'Rotate counter clockwise'**
431431
String get rotateCounterClockwise;
432+
433+
/// Import from image label
434+
///
435+
/// In en, this message translates to:
436+
/// **'Import from image'**
437+
String get importFromImage;
432438
}
433439

434440
class _AppLocalizationsDelegate

packages/mini_sprite_editor/lib/l10n/arb/app_localizations_en.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,7 @@ class AppLocalizationsEn extends AppLocalizations {
175175

176176
@override
177177
String get rotateCounterClockwise => 'Rotate counter clockwise';
178+
179+
@override
180+
String get importFromImage => 'Import from image';
178181
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import 'dart:ui';
2+
3+
import 'package:file_selector/file_selector.dart';
4+
import 'package:image/image.dart' as img;
5+
6+
class ImageImporterService {
7+
ImageImporterService();
8+
9+
Future<(List<List<int>> pixels, List<Color> colors)?> importImage() async {
10+
const typeGroup = XTypeGroup(
11+
label: 'images',
12+
extensions: <String>['jpg', 'png'],
13+
);
14+
final file = await openFile(
15+
acceptedTypeGroups: <XTypeGroup>[typeGroup],
16+
);
17+
18+
if (file != null) {
19+
final bytes = await file.readAsBytes();
20+
final image = img.decodeImage(bytes);
21+
22+
if (image != null) {
23+
final colors = <Color>[];
24+
25+
final pixels = List<List<int>>.generate(
26+
image.height,
27+
(_) => List<int>.generate(image.width, (index) => -1),
28+
);
29+
30+
for (var y = 0; y < image.height; y++) {
31+
for (var x = 0; x < image.width; x++) {
32+
final pixel = image.getPixelSafe(x, y);
33+
34+
final alpha = pixel.a.toInt();
35+
final red = pixel.r.toInt();
36+
final green = pixel.g.toInt();
37+
final blue = pixel.b.toInt();
38+
39+
final color = Color.fromARGB(alpha, red, green, blue);
40+
41+
if (!colors.contains(color) && color.a != 0) {
42+
colors.add(color);
43+
}
44+
45+
final colorIndex = colors.indexOf(color);
46+
pixels[y][x] = colorIndex;
47+
}
48+
}
49+
50+
return (pixels, colors);
51+
}
52+
}
53+
54+
return null;
55+
}
56+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export 'image_importer.dart';

packages/mini_sprite_editor/lib/sprite/view/sprite_page.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
55
import 'package:mini_sprite_editor/config/config.dart';
66
import 'package:mini_sprite_editor/l10n/l10n.dart';
77
import 'package:mini_sprite_editor/library/library.dart';
8+
import 'package:mini_sprite_editor/services/services.dart';
89
import 'package:mini_sprite_editor/sprite/cubit/tools_cubit.dart';
910
import 'package:mini_sprite_editor/sprite/sprite.dart';
1011
import 'package:mini_sprite_editor/widgets/composed_icon.dart';
@@ -250,6 +251,34 @@ class SpriteView extends StatelessWidget {
250251
tooltip: l10n.importFromClipBoard,
251252
icon: const Icon(Icons.import_export),
252253
),
254+
Tooltip(
255+
message: l10n.importFromImage,
256+
child: GestureDetector(
257+
key: const Key('import_image_key'),
258+
onTap: () async {
259+
final spriteCubit = context.read<SpriteCubit>();
260+
final configCubit = context.read<ConfigCubit>();
261+
262+
final scaffoldMessenger = ScaffoldMessenger.of(context);
263+
264+
final imageService =
265+
context.read<ImageImporterService>();
266+
final result = await imageService.importImage();
267+
if (result != null) {
268+
final (pixels, colors) = result;
269+
spriteCubit.setSprite(pixels);
270+
configCubit.setColors(colors);
271+
scaffoldMessenger.showSnackBar(
272+
SnackBar(content: Text(l10n.importSuccess)),
273+
);
274+
}
275+
},
276+
child: const ComposedIcon(
277+
primary: Icons.image,
278+
secondary: Icons.download,
279+
),
280+
),
281+
),
253282
IconButton(
254283
key: const Key('export_to_image'),
255284
onPressed: () async {

packages/mini_sprite_editor/pubspec.lock

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ packages:
1717
url: "https://pub.dev"
1818
source: hosted
1919
version: "7.7.1"
20+
archive:
21+
dependency: transitive
22+
description:
23+
name: archive
24+
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
25+
url: "https://pub.dev"
26+
source: hosted
27+
version: "4.0.7"
2028
args:
2129
dependency: transitive
2230
description:
@@ -340,6 +348,14 @@ packages:
340348
url: "https://pub.dev"
341349
source: hosted
342350
version: "9.1.5"
351+
image:
352+
dependency: "direct main"
353+
description:
354+
name: image
355+
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
356+
url: "https://pub.dev"
357+
source: hosted
358+
version: "4.5.4"
343359
intl:
344360
dependency: "direct main"
345361
description:
@@ -531,6 +547,14 @@ packages:
531547
url: "https://pub.dev"
532548
source: hosted
533549
version: "2.3.0"
550+
petitparser:
551+
dependency: transitive
552+
description:
553+
name: petitparser
554+
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
555+
url: "https://pub.dev"
556+
source: hosted
557+
version: "7.0.1"
534558
platform:
535559
dependency: transitive
536560
description:
@@ -555,6 +579,14 @@ packages:
555579
url: "https://pub.dev"
556580
source: hosted
557581
version: "1.5.1"
582+
posix:
583+
dependency: transitive
584+
description:
585+
name: posix
586+
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
587+
url: "https://pub.dev"
588+
source: hosted
589+
version: "6.0.3"
558590
provider:
559591
dependency: transitive
560592
description:
@@ -784,6 +816,14 @@ packages:
784816
url: "https://pub.dev"
785817
source: hosted
786818
version: "1.1.0"
819+
xml:
820+
dependency: transitive
821+
description:
822+
name: xml
823+
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
824+
url: "https://pub.dev"
825+
source: hosted
826+
version: "6.6.1"
787827
yaml:
788828
dependency: transitive
789829
description:

packages/mini_sprite_editor/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies:
2121
flutter_localizations:
2222
sdk: flutter
2323
hydrated_bloc: ^9.0.0-dev.3
24+
image: ^4.5.4
2425
intl: ^0.20.2
2526
mini_sprite: 0.1.0
2627
path_provider: ^2.0.11

0 commit comments

Comments
 (0)