11import 'dart:async' ;
2+ import 'dart:math' as math;
3+ import 'dart:typed_data' ;
24import 'dart:ui' as ui;
35import 'package:flutter/material.dart' ;
46import 'package:flutter/rendering.dart' ;
@@ -28,7 +30,9 @@ class _PictoTapScreenState extends State<PictoTapScreen> {
2830
2931 static const String _backgroundAssetPath =
3032 'assets/background/background.webp' ;
31- static const double _boardIconSize = 80 ;
33+ static const double _boardIconSize = 110 ;
34+ static const int _shareImageSize = 1080 ;
35+ static const Color _backgroundFallback = Color (0xFFF5F0EB );
3236 static const Duration _animationDuration = Duration (milliseconds: 450 );
3337
3438 void _addIcon (String icon) {
@@ -110,11 +114,51 @@ class _PictoTapScreenState extends State<PictoTapScreen> {
110114 as RenderRepaintBoundary ? ;
111115 if (boundary == null ) return ;
112116
113- final image = await boundary.toImage (pixelRatio: 3 );
114- final byteData = await image.toByteData (format: ui.ImageByteFormat .png);
115- if (byteData == null ) return ;
117+ const int out = _shareImageSize;
118+ final bSize = boundary.size;
119+ final pixelRatio = out / math.min (bSize.width, bSize.height);
120+ final rawImage = await boundary.toImage (pixelRatio: pixelRatio);
116121
117- final pngBytes = byteData.buffer.asUint8List ();
122+ final srcW = rawImage.width;
123+ final srcH = rawImage.height;
124+ final rawBytes = await rawImage.toByteData (
125+ format: ui.ImageByteFormat .rawStraightRgba,
126+ );
127+ if (rawBytes == null ) return ;
128+ final srcPixels = rawBytes.buffer.asUint8List ();
129+
130+ final cropSize = math.min (srcW, srcH);
131+ final cropX = (srcW - cropSize) ~ / 2 ;
132+ final cropY = (srcH - cropSize) ~ / 2 ;
133+
134+ final outPixels = Uint8List (out * out * 4 );
135+ for (var y = 0 ; y < out; y++ ) {
136+ final sy = (cropY + y * cropSize ~ / out).clamp (0 , srcH - 1 );
137+ for (var x = 0 ; x < out; x++ ) {
138+ final sx = (cropX + x * cropSize ~ / out).clamp (0 , srcW - 1 );
139+ final si = (sy * srcW + sx) * 4 ;
140+ final di = (y * out + x) * 4 ;
141+ outPixels[di] = srcPixels[si];
142+ outPixels[di + 1 ] = srcPixels[si + 1 ];
143+ outPixels[di + 2 ] = srcPixels[si + 2 ];
144+ outPixels[di + 3 ] = srcPixels[si + 3 ];
145+ }
146+ }
147+
148+ final completer = Completer <ui.Image >();
149+ ui.decodeImageFromPixels (
150+ outPixels,
151+ out,
152+ out,
153+ ui.PixelFormat .rgba8888,
154+ completer.complete,
155+ );
156+ final outputImage = await completer.future;
157+ final pngData =
158+ await outputImage.toByteData (format: ui.ImageByteFormat .png);
159+ if (pngData == null ) return ;
160+
161+ final pngBytes = pngData.buffer.asUint8List ();
118162 final fileName =
119163 'pictotap-board-${DateTime .now ().millisecondsSinceEpoch }.png' ;
120164 await saveAndShareImage (pngBytes, fileName, shareText);
@@ -223,30 +267,29 @@ class _PictoTapScreenState extends State<PictoTapScreen> {
223267 ),
224268 foregroundColor: Colors .white,
225269 ),
226- body: Stack (
270+ body: Column (
227271 children: [
228- Positioned . fill (
229- child: Image . asset (
230- _backgroundAssetPath ,
231- fit : BoxFit .cover,
232- errorBuilder : (context, error, stackTrace) {
233- return Container (color : const Color ( 0xFFF5F0EB ));
234- },
235- ),
236- ),
237- Positioned . fill (
238- child : Container (color : Colors .white. withAlpha ( 140 )) ,
239- ),
240- Column (
241- children : [
242- Expanded (
243- child : GestureDetector (
244- onTap : _toggleKeyboard,
245- child: RepaintBoundary (
246- key : _boardBoundaryKey ,
247- child : Container (
272+ Expanded (
273+ child: GestureDetector (
274+ onTap : _toggleKeyboard ,
275+ child : RepaintBoundary (
276+ key : _boardBoundaryKey,
277+ child : Stack (
278+ children : [
279+ Positioned . fill (
280+ child : Image . asset (
281+ _backgroundAssetPath,
282+ fit : BoxFit .cover ,
283+ errorBuilder : (context, error, stackTrace) {
284+ return Container (color : _backgroundFallback);
285+ },
286+ ),
287+ ),
288+ Positioned . fill (
289+ child: Container (color : Colors .white. withAlpha ( 140 )),
290+ ) ,
291+ SizedBox (
248292 width: double .infinity,
249- color: Colors .transparent,
250293 child: _selectedIcons.isEmpty
251294 ? Center (
252295 child: BoardEmptyHint (
@@ -267,19 +310,19 @@ class _PictoTapScreenState extends State<PictoTapScreen> {
267310 ),
268311 ),
269312 ),
270- ) ,
313+ ] ,
271314 ),
272315 ),
273- if (_isKeyboardVisible)
274- PictogramKeyboard (
275- onIconSelected: _addIcon,
276- onSpace: _addSpace,
277- onBackspace: _removeLast,
278- recommendations: buildRecommendations (_selectedIcons),
279- showLimitBanner: _showLimitReachedBanner,
280- ),
281- ],
316+ ),
282317 ),
318+ if (_isKeyboardVisible)
319+ PictogramKeyboard (
320+ onIconSelected: _addIcon,
321+ onSpace: _addSpace,
322+ onBackspace: _removeLast,
323+ recommendations: buildRecommendations (_selectedIcons),
324+ showLimitBanner: _showLimitReachedBanner,
325+ ),
283326 ],
284327 ),
285328 floatingActionButton: _isKeyboardVisible
0 commit comments