c# - Clip BitmapImage using Strokes from a InkCanvas -
i'm tasked create "cinemagraph" feature, user must select desired area using inkcanvas draw selected pixels should remain untouched rest of animation/video (or, select pixels should "alive").
i'm thinking getting stroke collection inkcanvas , use clip image , merge untouched one.
how can that? can load images disk, how can clip image based on stroke?
more details:
after drawing , selecting pixels should remain static, have stroke collection. can geometry of each individual stroke, need merge geometries.
based on merged geometry, need invert (the geometry) , use clip first frame, later clipped image ready, need merge other frames.
my code far:
//gets bitmapsource string path: var image = listframes[0].imagelocation.sourcefrom(); var rectangle = new rectanglegeometry(new rect(new system.windows.point(0, 0), new system.windows.size(image.width, image.height))); geometry geometry = geometry.empty; foreach(stroke stroke in cinemagraphinkcanvas.strokes) { geometry = geometry.combine(geometry, stroke.getgeometry(), geometrycombinemode.union, null); } //inverts geometry, clip other unselect pixels of bitmapimage. geometry = geometry.combine(geometry, rectangle, geometrycombinemode.exclude, null); //this here uielement, can't use control, need way clip image without using ui. var clippedimage = new system.windows.controls.image(); clippedimage.source = image; clippedimage.clip = geometry; //i can't render of clippedimage control because i'm not displaying control. is there way clip bitmapsource without using uielement?
maybe, maybe
i'm thinking opacitymask , brush... can't use uielement, need apply opacitymask directly bitmapsource.
i made it! (you can see result here, screentogif > editor > image tab > cinemagraph)
code
sourcefrom() , dpiof() , scaledsize():
/// <summary> /// gets bitmapsource source , closes file usage. /// </summary> /// <param name="filesource">the file open.</param> /// <param name="size">the maximum height of image.</param> /// <returns>the open bitmapsource.</returns> public static bitmapsource sourcefrom(this string filesource, int32? size = null) { using (var stream = new filestream(filesource, filemode.open)) { var bitmapimage = new bitmapimage(); bitmapimage.begininit(); bitmapimage.cacheoption = bitmapcacheoption.onload; if (size.hasvalue) bitmapimage.decodepixelheight = size.value; //dpiof() , scaledsize() uses same principles of extension. bitmapimage.streamsource = stream; bitmapimage.endinit(); //just in case want load image in thread. bitmapimage.freeze(); return bitmapimage; } } getrender():
/// <summary> /// gets render of current uielement /// </summary> /// <param name="source">uielement screenshot</param> /// <param name="dpi">the dpi of source.</param> /// <returns>an imagesource</returns> public static rendertargetbitmap getrender(this uielement source, double dpi) { rect bounds = visualtreehelper.getdescendantbounds(source); var scale = dpi / 96.0; var width = (bounds.width + bounds.x) * scale; var height = (bounds.height + bounds.y) * scale; #region if no bounds if (bounds.isempty) { var control = source control; if (control != null) { width = control.actualwidth * scale; height = control.actualheight * scale; } bounds = new rect(new system.windows.point(0d, 0d), new system.windows.point(width, height)); } #endregion var roundwidth = (int)math.round(width, midpointrounding.awayfromzero); var roundheight = (int)math.round(height, midpointrounding.awayfromzero); var rtb = new rendertargetbitmap(roundwidth, roundheight, dpi, dpi, pixelformats.pbgra32); drawingvisual dv = new drawingvisual(); using (drawingcontext ctx = dv.renderopen()) { visualbrush vb = new visualbrush(source); var locationrect = new system.windows.point(bounds.x, bounds.y); var sizerect = new system.windows.size(bounds.width, bounds.height); ctx.drawrectangle(vb, null, new rect(locationrect, sizerect)); } rtb.render(dv); return (rendertargetbitmap)rtb.getasfrozen(); } gets imagesource , geometry:
//custom extensions, using path of image, provide //dpi (of image) , scaled size (pixelwidth , pixelheight). var dpi = listframes[0].imagelocation.dpiof(); var scaledsize = listframes[0].imagelocation.scaledsize(); //custom extension loads first frame. var image = listframes[0].imagelocation.sourcefrom(); //rectangle same size of image. used within xor operation. var rectangle = new rectanglegeometry(new rect( new system.windows.point(0, 0), new system.windows.size(image.pixelwidth, image.pixelheight))); geometry geometry = geometry.empty; //each stroke transformed geometry , combined union operation. foreach(stroke stroke in cinemagraphinkcanvas.strokes) { geometry = geometry.combine(geometry, stroke.getgeometry(), geometrycombinemode.union, null); } //the rectangle same size of image combined of //the strokes using xor operation, inverts geometry. geometry = geometry.combine(geometry, rectangle, geometrycombinemode.xor, null); applying geometry image element:
//uielement used hold bitmapsource clipped. var clippedimage = new system.windows.controls.image { height = image.pixelheight, width = image.pixelwidth, source = image, clip = geometry }; clippedimage.measure(scaledsize); clippedimage.arrange(new rect(scaledsize)); //gets render of image element, clipped. var imagerender = clippedimage.getrender(dpi, scaledsize); //merging frames: overlay(imagerender, dpi, true); overlay(), merges frames:
private void overlay(rendertargetbitmap render, double dpi, bool forall = false) { //gets selected frames based on selection of listview, //in case, every frame should selected. var framelist = forall ? listframes : selectedframes(); int count = 0; foreach (frameinfo frame in framelist) { var image = frame.imagelocation.sourcefrom(); var drawingvisual = new drawingvisual(); using (drawingcontext drawingcontext = drawingvisual.renderopen()) { drawingcontext.drawimage(image, new rect(0, 0, image.width, image.height)); drawingcontext.drawimage(render, new rect(0, 0, render.width, render.height)); } //converts visual (drawingvisual) bitmapsource var bmp = new rendertargetbitmap(image.pixelwidth, image.pixelheight, dpi, dpi, pixelformats.pbgra32); bmp.render(drawingvisual); //creates bmpbitmapencoder , adds bitmapsource frames of encoder var encoder = new bmpbitmapencoder(); encoder.frames.add(bitmapframe.create(bmp)); //saves image file using encoder using (stream stream = file.create(frame.imagelocation)) encoder.save(stream); } } example:
clean, unedited animation.
selected pixels should animated.
image clipped (black transparent).
cinemagraph done!
as can see, selected pixels can change, others remain static.





Comments
Post a Comment