Canvas drawImage scale height and width in CustomPainter - image

I am developing an application in Flutter where I am using CustomPainter to draw an image which the user picks from gallery/camera. In addition to this the use can draw lines as well as change the stroke value, opacity colour and colour on it its own. For this I have created 2 classes DrawEditor and DrawingPainter the code for those two classes can be found below. Once the user picks an image
the image is passed to the DrawingPainter class where paint() is called and I draw my lines and image. The issue is in _paintBackgroundImage() in this method I draw the image by using canvas.drawImage(paintedImage, Offset.zero, Paint()); which does not scale the image.
Earlier I tried a different approach instead of drawing the image with canvas.drawImage(paintedImage, Offset.zero, Paint()) I used canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint()); as can be seen below. However with this approach the draw picture Is pixelated so I prefer canvas.drawImage(paintedImage, Offset.zero, Paint()) as this does not damage the picture.
Any help with scaling the image will be greatly appreciated.
//Example 1 : Code with canvas.drawImageRect but image pixelated
final UI.Rect rect = UI.Offset.zero & _canvasSize;
final Size imageSize =Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
final Rect inputSubRect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubRect =
Alignment.center.inscribe(sizes.destination, rect);
canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
//Example 2 : Code with canvas.drawImageRect but image pixelated
canvas.drawRect(Rect.fromPoints(blurStartOffset, blurIndicatorOffset),
blurPaintSettings)
class DrawingPainter extends CustomPainter {
static int blurColor = 0xFFB3E5FC;
UI.Image paintedImage;
List<DrawingPoints> pointsList;
List<DrawingPoints> blurPointsList;
List<Offset> offsetPoints = List();
Size _canvasSize;
Offset blurIndicatorOffset;
Offset blurStartOffset;
bool isBlur;
List<BlurIndicatorOffsetWrapper> wrapperList = new List();
/// To blur an image we need a [MaskFilter]
Paint blurPaintSettings = new Paint()
..style = PaintingStyle.fill
..color = Color(blurColor)
..maskFilter = MaskFilter.blur(BlurStyle.normal, 3.0);
DrawingPainter(
{this.pointsList,
this.paintedImage,
this.blurPointsList,
this.blurIndicatorOffset,
this.blurStartOffset}) {
isBlur = blurIndicatorOffset != null;
}
#override
void paint(Canvas canvas, Size size) {
_canvasSize = size;
_paintBackgroundImage(canvas);
_drawPoints(canvas);
_drawBlurIndicator(canvas);
}
/// Paints the image onto the canvas
void _paintBackgroundImage(Canvas canvas) {
if (paintedImage == null) {
return;
}
final UI.Rect rect = UI.Offset.zero & _canvasSize;
final Size imageSize =
Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
FittedSizes sizes = applyBoxFit(BoxFit.contain, imageSize, _canvasSize);
final Rect inputSubRect =
Alignment.center.inscribe(sizes.source, Offset.zero & imageSize);
final Rect outputSubRect =
Alignment.center.inscribe(sizes.destination, rect);
canvas.drawImageRect(paintedImage, inputSubRect, outputSubRect, Paint());
}
/// Paints the lines onto the canvas
void _drawPoints(Canvas canvas) {
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(pointsList[i].points, pointsList[i + 1].points,
pointsList[i].paint);
}
}
}
/// Paints the blur indicator onto the canvas
void _drawBlurIndicator(Canvas canvas) {
if (blurStartOffset != null && blurIndicatorOffset != null) {
canvas.drawRect(Rect.fromPoints(blurStartOffset, blurIndicatorOffset),
blurPaintSettings);
}
}
void setBlurIndicator(Offset localOffset) {
blurIndicatorOffset = localOffset;
}
#override
bool shouldRepaint(DrawingPainter oldDelegate) {
return true;
}
Future<Uint8List> save() async {
//Create canvas
// Set PictureRecorder on the canvas and start recording
UI.PictureRecorder recorder = UI.PictureRecorder();
Canvas canvas = Canvas(recorder);
//Draw image on new canvas
if (paintedImage != null) {
final Size imageSize = Size(paintedImage.width.toDouble(), paintedImage.height.toDouble());
//Here image is the problem
canvas.drawImage(paintedImage, Offset.zero, Paint());
}
//Draw points on new canvas
for (int i = 0; i < pointsList.length - 1; i++) {
if (pointsList[i] != null && pointsList[i + 1] != null) {
canvas.drawLine(
pointsList[i].points,
pointsList[i + 1].points,
pointsList[i].paint,
);
}
}
//End recording
final resultImage = await recorder.endRecording().toImage(
_canvasSize.width.floor(),
_canvasSize.height.floor(),
);
final imageBytes =
await resultImage.toByteData(format: UI.ImageByteFormat.png);
return imageBytes.buffer.asUint8List();
}
}
class DrawingPoints {
Paint paint;
Offset points;
DrawingPoints({this.points, this.paint});
}
enum SelectedMode { StrokeWidth, Opacity, Color, Blur }

I had a very similar requirement and the comment about using paintImage was exactly what I was looking for, so I figured I'd share what I ended up with.
I needed to scale down an image and draw overlays on top of that image. image is my original (unscaled) Image object.
var recorder = ui.PictureRecorder();
var imageCanvas = new Canvas(recorder);
var painter = _MarkupPainter(_overlays);
//Paint the image into a rectangle that matches the requested width/height.
//This will handle rescaling the image into the rectangle so that it will not be clipped.
paintImage(
canvas: imageCanvas,
rect: Rect.fromLTWH(0, 0, scaledWidth, scaledHeight),
image: image,
fit: BoxFit.scaleDown,
repeat: ImageRepeat.noRepeat,
scale: 1.0,
alignment: Alignment.center,
flipHorizontally: false,
filterQuality: FilterQuality.high
);
//Add the markup overlays.
painter.paint(imageCanvas, Size(scaledWidth, scaledHeight));
var picture = recorder.endRecording();
return picture.toImage(scaledWidth.toInt(), scaledHeight.toInt());

Related

Image auto-cropping, issue with borders

I'm talking photo in my application. After I take it, on next screen in layout I want it be automatically cropped like in image.
But I'm constantly lose the boundaries of the photo and I have strange borders (indicated by RED in the image).
Here is my code:
Android code
private void NewElement_OnDrawBitmap(object sender, EventArgs e)
{
if (this.ViewGroup != null)
{
//get the subview
Android.Views.View subView = ViewGroup.GetChildAt(0);
int width = subView.Width;
int height = subView.Height;
//create and draw the bitmap
Bitmap b = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888);
Canvas c = new Canvas(b);
ViewGroup.Draw(c);
//save the bitmap to file
bytes = SaveBitmapToFile(b);
}
}
iOS code
UIGraphics.BeginImageContextWithOptions(this.Bounds.Size, true, 0);
this.Layer.RenderInContext(UIGraphics.GetCurrentContext());
var img = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
using (NSData imageData = img.AsPNG())
{
bytes = new Byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, bytes, 0, Convert.ToInt32(imageData.Length));
}
private byte[] SaveBitmapToFile(Bitmap bm)
{
MemoryStream ms = new MemoryStream();
bm.Compress(Bitmap.CompressFormat.Png, 100, ms);
return ms.ToArray();
}
}
From your shared code, you're not doing any cropping, so your app just put your origin picture into the view, and the border you mentioned, is probably the background of your view.
And your project code, I saw you added
Bitmap bitmap = Bitmap.CreateBitmap(b, 0, 0, (98 * width) / 100, (82 * height) / 100);
Yes this is cropping the picture, but this just crop the image from top-left corner to 98% width and 82% height. much like
If you want to crop the picture into a square focused in center, you can simply:
int offset = (height - width) / 2;
Bitmap bitmap = Bitmap.CreateBitmap(b, 0, offset, width, height- offset);
But if you want to do more operations on Image like move/zoom... and then crop, I'd like to suggest you turn to existing solutions like FFImageLoading or Syncfusion. Or you'll have to calculate all the move/zoom data to do the crop.

How to crop the png image and remove its unused space using Canvas in flutter?

This attachment is from the rendered canvas image which is saved locally via canvas. In image I have drawn the square box which I want to render in canvas and save locally without left and right extra spaces. I just want to save the square box and remove that unnecessary space of PNG-image. So, how to do this?
widget-source-code:
return CustomPaint(
painter: PngImageCropper(image: image),
);
PngImageCropper-code
class PngImageCropper extends CustomPainter {
PngImageCropper({
this.image,
});
ui.Image image;
#override
void paint(Canvas canvas, Size size) {
_drawCanvas(size, canvas);
_saveCanvas(size);
}
Canvas _drawCanvas(Size size, Canvas canvas) {
final center = Offset(image.width / 2, image.height / 2);
double drawImageWidth = 0;
double drawImageHeight = 0;
Rect rect =
Rect.fromCircle(center: center, radius: _getCircularRadius(image));
Path path = Path()..addOval(rect);
canvas.clipPath(path);
Paint paint = new Paint();
canvas.drawImage(
image,
Offset(drawImageWidth, drawImageHeight),
paint,
);
return canvas;
}
_getCircularRadius(ui.Image image) {
return image.height > image.width
? image.width.toDouble() / 2
: image.height.toDouble() / 2;
}
_saveCanvas(Size size) async {
var pictureRecorder = ui.PictureRecorder();
var canvas = Canvas(pictureRecorder);
var paint = Paint();
paint.isAntiAlias = true;
_drawCanvas(size, canvas);
var pic = pictureRecorder.endRecording();
ui.Image img = await pic.toImage(image.width, image.height);
var byteData = await img.toByteData(format: ui.ImageByteFormat.png);
var buffer = byteData.buffer.asUint8List();
// var response = await get(imgUrl);
var documentDirectory = await getApplicationDocumentsDirectory();
File file = File(join(documentDirectory.path,
'${DateTime.now().toUtc().toIso8601String()}.png'));
file.writeAsBytesSync(buffer);
print(file.path);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Worked source code! Answer by #pskink.
Future<List<int>> cropRonded(ui.Image image) async {
var recorder = ui.PictureRecorder();
var canvas = Canvas(recorder);
var imageSize = Size(image.width.toDouble(), image.height.toDouble());
var boundsToCrop = Rect.fromCenter(
center: imageSize.center(Offset.zero),
width: imageSize.shortestSide,
height: imageSize.shortestSide);
var matrix = Matrix4.translationValues(
-boundsToCrop.topLeft.dx, -boundsToCrop.topLeft.dy, 0)
.storage;
var paint = Paint()
..shader = ImageShader(image, TileMode.clamp, TileMode.clamp, matrix);
var radius = imageSize.shortestSide / 2;
canvas.drawCircle(Offset(radius, radius), radius, paint);
ui.Image cropped = await recorder
.endRecording()
.toImage(imageSize.shortestSide.toInt(), imageSize.shortestSide.toInt());
var byteData = await cropped.toByteData(format: ui.ImageByteFormat.png);
return byteData.buffer.asUint8List();
}

openseadragon get selection dataurl/blob

I retrieve a rect from openSeadragonSelection:
viewer:
this.viewer = OpenSeadragon(this.config);
this.selection = this.viewer.selection({
showConfirmDenyButtons: true,
styleConfirmDenyButtons: true,
returnPixelCoordinates: true,
onSelection: rect => console.log(rect)
});
this.selection.enable();
rect by onSelection:
t.SelectionRect {x: 3502, y: 2265, width: 1122, height: 887, rotation:0, degrees: 0, …}
I have no idea how to get the canvas by rect from my viewer instance.
this.viewer.open(new OpenSeadragon.ImageTileSource(this.getTile(this.src)));
A self implemented imageViewer returned the canvas of the selected area. So I could get the blob and post it to the server:
onSave(canvas){
let source = canvas.toDataURL();
this.setState({source:source, crop: false, angle: 0});
save(this.dataURItoBlob(source), source.match(new RegExp("\/(.*);"))1]);
}
dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
How can I get the image of the viewer by rect. Rotation should be considered as well.
#iangilman:
Thank's alot for your advice. I created another canvas which I crop and after that put it back into the viewer. I was not sure if something similar was supported by your library yet:
const viewportRect = self.viewer.viewport.imageToViewportRectangle(rect);
const webRect = self.viewer.viewport.viewportToViewerElementRectangle(viewportRect);
const { x, y, width, height } = webRect || {};
const { canvas } = self.viewer.drawer;
let source = canvas.toDataURL();
const img = new Image();
img.onload = function () {
let croppedCanvas = document.createElement('canvas');
let ctx = croppedCanvas.getContext('2d');
croppedCanvas.width = width;
croppedCanvas.height = height;
ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
let croppedSrc = croppedCanvas.toDataURL();
//update viewer with cropped image
self.tile = self.getTile(croppedSrc);
self.ImageTileSource = new OpenSeadragon.ImageTileSource(self.tile);
self.viewer.open(self.ImageTileSource);
}
img.src = source;
Rotation hasn't been considered yet.
I imagine you'll need to convert the rectangle into the proper coordinates, then create a second canvas and copy the appropriate bit out of the OSD canvas into the second one.
Looks like maybe the selection rectangle is in image coordinates? The OSD canvas will be in web coordinates, or maybe double that on an HDPI display. OSD has a number of conversion functions, for instance:
var viewportRect = viewer.viewport.imageToViewportRectangle(imageRect);
var webRect = viewer.viewport.viewportToViewerElementRectangle(viewportRect);
You can find out the pixel density via OpenSeadragon.pixelDensityRatio.
Once you have the appropriate rectangle it should be easy to copy out of the one canvas into another. I'm not sure how you incorporate rotation, but it might be as simple as adding a rotation call to one of the canvas contexts.
Sorry this is kind of vague, but I hope it helps!

How to set 3 components in one Horizontal field without adjusting editfield width

HorizontalFieldManager hfm = new HorizontalFieldManager();
this.add(hfm);
LabelField lblheight = new LabelField("Height");
EditField lField = new EditField() {
protected void layout(int width, int height) {
super.layout(width, height);
this.setExtent(200, this.getHeight());
}
public int getPreferredWidth() {
return 200;
}
};
Background editFieldBackground = BackgroundFactory
.createSolidBackground(0X00F7F7FF);
XYEdges edges = new XYEdges(5, 5, 5, 5);
Border border = BorderFactory.createRoundedBorder(edges, 0X00D6DBDE,
Border.STYLE_FILLED);
Bitmap switchOn = Bitmap.getBitmapResource("switch_left.png");
Bitmap switchOff = Bitmap.getBitmapResource("switch_right.png");
Bitmap switchOnFocus = Bitmap
.getBitmapResource("switch_left_focus.png");
Bitmap switchOffFocus = Bitmap
.getBitmapResource("switch_right_focus.png");
SwitchField sw = new SwitchField(switchOn, switchOff, switchOnFocus,
switchOffFocus, true);
lField.setBackground(editFieldBackground);
lField.setBorder(border);
hfm.add(lblheight);
hfm.add(lField);
hfm.add(sw);
I used the above code for setting 3 components in one horizontal field manager, but my issue is that here I set the width for the edittext.
So, is there any other option to display 3 components in the proper manner on all devices, without adjusting the width of the edittext?

Haxe NME Resizing a Bitmap

I am trying to learn NME with haxe to create a small game. I have setup NME 3.5.5 with Haxe 2.10 in FlashDevelop. To draw the game background, I'm using
// Class level variable
var background : nme.display.Bitmap;
public function initResources() : Void
{
background = new Bitmap(Assets.getBitmapData("img/back.png"));
}
And in the render loop, I'm rendering like this.
g.clear();
g.beginBitmapFill(background.bitmapData, true, true);
g.drawRect(0, 0, 640, 480);
g.endFill();
This is drawing the image across the view and I need to resize it to fit the screen.
EDIT:
Here is the function i'm using to scale the bitmap. It doesn't work and nothing is rendered on the screen.
public static function resize( source:Bitmap, width:Int, height:Int ) : Bitmap
{
var scaleX:Int = Std.int(width / source.bitmapData.width);
var scaleY:Int = Std.int(height / source.bitmapData.height);
var data:BitmapData = new BitmapData(width, height, true);
var matrix:Matrix = new Matrix();
matrix.scale(scaleX, scaleY);
data.draw(source.bitmapData, matrix);
return new Bitmap(data);
}
Thanks.
EDIT 2:
Finally made it. I was unnecessarily casting it to int. Here's the solution.
public static function resize( source:Bitmap, width:Int, height:Int ) : Bitmap
{
var scaleX:Float = width / source.bitmapData.width;
var scaleY:Float = height / source.bitmapData.height;
var data:BitmapData = new BitmapData(width, height, true);
var matrix:Matrix = new Matrix();
matrix.scale(scaleX, scaleY);
data.draw(source.bitmapData, matrix);
return new Bitmap(data);
}
Look this Resizing BitmapData in ActionScript 3
You should use BitmapData.draw and scaled matrix.

Resources