如何在Xamarin.Forms中将SKSurface转为SKBitmap并生成1060×550尺寸图片?
Hey there! Let's work through your problem of generating a precise 1060×550 image from your Xamarin.Forms SKCanvasView, regardless of device screen size. Here's a reliable approach that keeps your drawing accurate and the final image exactly the dimensions you need.
First: Configure Your SKCanvasView Correctly
First off, keep IgnorePixelScaling="True"—this is important! It decouples the canvas coordinate system from the device's DPI, so you're working with raw pixel values, which makes it way easier to control the final image size. Your XAML is already on the right track:
<StackLayout> <skia:SKCanvasView x:Name="canvasView" PaintSurface="ImageCanvas_PaintSurface" EnableTouchEvents="True" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" IgnorePixelScaling="True" Touch="OnTouch" /> <Button Text="Generate Image" Clicked="CreateButton_Clicked"/> </StackLayout>
Core Idea: Use a "Working Bitmap" for Drawing
Instead of drawing directly to the SKCanvasView's surface (which adapts to the device screen), we'll maintain a separate SKBitmap with your target 1060×550 dimensions. All user drawing happens on this bitmap, and we just render a scaled version of it to the view for display. This way, when you export, you're using the exact-sized bitmap directly.
Step 1: Initialize Your Working Canvas
Add these fields to your page code-behind, then set up the working bitmap and paint in your constructor:
private SKBitmap _workingBitmap; private SKCanvas _workingCanvas; private SKPaint _drawPaint; private SKPath _currentDrawPath; private float _displayScale; private float _displayOffsetX; private float _displayOffsetY; public YourPage() { InitializeComponent(); // Create our working bitmap with the exact target dimensions _workingBitmap = new SKBitmap(1060, 550); _workingCanvas = new SKCanvas(_workingBitmap); // Configure your drawing paint (adjust color, width, etc. as needed) _drawPaint = new SKPaint { Color = SKColors.Black, StrokeWidth = 5, Style = SKPaintStyle.Stroke, IsAntialias = true, StrokeCap = SKStrokeCap.Round // For smoother lines }; }
Step 2: Render the Working Bitmap to the View
In the PaintSurface event, scale and center your working bitmap to fit the SKCanvasView's size, so users see a clear, properly scaled preview:
private void ImageCanvas_PaintSurface(object sender, SKPaintSurfaceEventArgs e) { var canvas = e.Surface.Canvas; canvas.Clear(SKColors.White); // Start with a white background // Calculate the best scale to fit the working bitmap in the view (maintain aspect ratio) float scaleX = canvasView.CanvasSize.Width / _workingBitmap.Width; float scaleY = canvasView.CanvasSize.Height / _workingBitmap.Height; _displayScale = Math.Min(scaleX, scaleY); // Calculate offsets to center the bitmap in the view _displayOffsetX = (canvasView.CanvasSize.Width - _workingBitmap.Width * _displayScale) / 2; _displayOffsetY = (canvasView.CanvasSize.Height - _workingBitmap.Height * _displayScale) / 2; // Apply scaling and offset, then draw the working bitmap canvas.Save(); canvas.Translate(_displayOffsetX, _displayOffsetY); canvas.Scale(_displayScale); canvas.DrawBitmap(_workingBitmap, 0, 0); canvas.Restore(); }
Step 3: Handle Touch Events with Coordinate Conversion
Since the view shows a scaled version of your working bitmap, you need to convert touch coordinates from the view's space to the working bitmap's raw pixel space. Here's how to do that:
private void OnTouch(object sender, SKTouchEventArgs e) { // Convert touch location to the working bitmap's coordinate system float bitmapX = (e.Location.X - _displayOffsetX) / _displayScale; float bitmapY = (e.Location.Y - _displayOffsetY) / _displayScale; switch (e.ActionType) { case SKTouchAction.Pressed: _currentDrawPath = new SKPath(); _currentDrawPath.MoveTo(bitmapX, bitmapY); break; case SKTouchAction.Moved: if (_currentDrawPath != null) { _currentDrawPath.LineTo(bitmapX, bitmapY); _workingCanvas.DrawPath(_currentDrawPath, _drawPaint); canvasView.InvalidateSurface(); // Refresh the view to show the new drawing } break; case SKTouchAction.Released: _currentDrawPath = null; break; } e.Handled = true; }
Step 4: Export the Exact-Sized Image
Finally, in your button click handler, save the working bitmap directly—since it's already 1060×550, no scaling is needed:
private void CreateButton_Clicked(object sender, EventArgs e) { try { using (var image = SKImage.FromBitmap(_workingBitmap)) using (var encodedData = image.Encode(SKEncodedImageFormat.Png, 100)) { // Save to an accessible location (adjust path as needed) string savePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "custom_drawing.png" ); using (var stream = File.OpenWrite(savePath)) { encodedData.SaveTo(stream); } DisplayAlert("Success!", $"Image saved to:\n{savePath}", "OK"); } } catch (Exception ex) { DisplayAlert("Error", $"Failed to save image: {ex.Message}", "OK"); } }
Don't Forget Cleanup
To avoid memory leaks, dispose of the SK objects when the page is no longer used:
protected override void OnDisappearing() { base.OnDisappearing(); _workingBitmap?.Dispose(); _workingCanvas?.Dispose(); _drawPaint?.Dispose(); }
Key Notes
- The working bitmap ensures your drawing is always in the target dimensions, so export is pixel-perfect.
- Coordinate conversion guarantees that user touches map exactly to the right spot on the final image, no matter the device screen size.
IgnorePixelScalingeliminates DPI-related confusion, making scaling calculations straightforward.
内容的提问来源于stack exchange,提问作者James Baxter

