I have an Image on my wpf control
and I am trying to generate croped part of it - this is ok more or less.
I have used a codeproject solution to generate BitmapSource of croped image (http://www.codeproject.com/KB/WPF/CropAdorner.aspx) but when I am trying to
replace current image with generated BitmapSource like this
imgCurrent.Source = generatedBitmapSource;
I see very strange behaviour ((
I need an advice how to change current Image with new based on BitmapSource.
my XAML(there is nothing extraordinary - and by the right click I am trying to replace currentImage with croped):
<DockPanel Height="395" Width="926">
<!--Went with a DockPanel here so that the image would always be centered in its parent control.-->
<Image x:Name="imgCurrent" VerticalAlignment="Center" HorizontalAlignment="Center" MouseRightButtonDown="imgCurrent_MouseRightButtonDown"/>
</DockPanel>
right click:
private void imgCurrent_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
generatedBitmapSource = _clp.BpsCrop();
//this clears croping adonder
AdornerLayer aly = AdornerLayer.GetAdornerLayer(_felCur);
aly.Remove(_clp);
//
imageCurrent.Source = generatedBitmapSource;
}
croping method (from codeproject):
public BitmapSource BpsCrop()
{
Thickness margin = AdornerMargin();
Rect rcInterior = _prCropMask.RectInterior;
Point pxFromSize = UnitsToPx(rcInterior.Width, rcInterior.Height);
// It appears that CroppedBitmap indexes from the upper left of the margin whereas RenderTargetBitmap renders the
// control exclusive of the margin. Hence our need to take the margins into account here...
Point pxFromPos = UnitsToPx(rcInterior.Left + margin.Left, rcInterior.Top + margin.Top);
Point pxWhole = UnitsToPx(AdornedElement.RenderSize.Width + margin.Left, AdornedElement.RenderSize.Height + margin.Left);
pxFromSize.X = Math.Max(Math.Min(pxWhole.X - pxFromPos.X, pxFromSize.X), 0);
pxFromSize.Y = Math.Max(Math.Min(pxWhole.Y - pxFromPos.Y, pxFromSize.Y), 0);
if (pxFromSize.X == 0 || pxFromSize.Y == 0)
{
return null;
}
System.Windows.Int32Rect rcFrom = new System.Windows.Int32Rect(pxFromPos.X, pxFromPos.Y, pxFromSize.X, pxFromSize.Y);
RenderTargetBitmap rtb = new RenderTargetBitmap(pxWhole.X, pxWhole.Y, s_dpiX, s_dpiY, PixelFormats.Default);
rtb.Render(AdornedElement);
return new CroppedBitmap(rtb, rcFrom);
}
Are you sure you don't have issue with your "BitmapSource of croped image"? Can you for test purpose replace it with another valid Bitmap and try if it works. If it works with another one, but not "BitmapSource of croped image" then maybe you have issue with creating "BitmapSource of croped image".
Related
I am applying Win2D gaussian blur on an Image File(Storage file) and directly on an UIElement - Image (Which is a XAML image) and I see that for the same value for the BlurAmount I am getting different output for the Storage file output and the XAML Image ..
Original Image
Output when 100% blur applied to XAML Image (Or any UIElement)
Output when 100% blur applied to a Storage file
Relevant Code :
For image File :
using (var stream = await originalFile.OpenAsync(FileAccessMode.Read))
{
var device = new CanvasDevice();
var bitmap = await CanvasBitmap.LoadAsync(device, stream);
var renderer = new CanvasRenderTarget(device, bitmap.SizeInPixels.Width, bitmap.SizeInPixels.Height, bitmap.Dpi);
using (var ds = renderer.CreateDrawingSession())
{
var blur = new GaussianBlurEffect();
blur.BlurAmount = eo.effectAmount1;
blur.BorderMode = EffectBorderMode.Hard;
blur.Source = bitmap;
ds.DrawImage(blur);
}
var saveFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync("ImageName.jpg", CreationCollisionOption.GenerateUniqueName);
using (var outStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
{
await renderer.SaveAsync(outStream, CanvasBitmapFileFormat.Png,1.0f);
}
}
For UIElement(XAML Image) :
using (var stream = await sourceElement.RenderToRandomAccessStream())
{
var device = new CanvasDevice();
var bitmap = await CanvasBitmap.LoadAsync(device, stream);
var renderer = new CanvasRenderTarget(device,bitmap.SizeInPixels.Width,
bitmap.SizeInPixels.Height,
bitmap.Dpi);
using (var ds = renderer.CreateDrawingSession())
{
var blur = new GaussianBlurEffect();
blur.BlurAmount = blurAmount;
blur.Source = bitmap;
blur.BorderMode = EffectBorderMode.Hard;
ds.DrawImage(blur);
}
stream.Seek(0);
await renderer.SaveAsync(stream, CanvasBitmapFileFormat.Png);
}
Question : Is this the expected behaviour ? If yes, how can I make both the cases have the same output ? If no, what am I doing wrong ?
Can you try using bitmap.Size instead of bitmap.SizeInPixels?
var renderer = new CanvasRenderTarget(bitmap, bitmap.Size);
I'm guessing you're experiencing blur effect plus pixelation.
Edit 1
And if that doesn't work, put this assertion after you create your renderer. SizeInPixels needs to match:
Debug.Assert(
bitmap.SizeInPixels.Width == renderer.SizeInPixels.Width
&& bitmap.SizeInPixels.Height == renderer.SizeInPixels.Height
);
Edit 2
And if pixels are ok, try setting the StretchMode on the image element to None.
<Image Stretch="None" />
I'm just suspicious of pixelation somewhere in the visual tree.
Explanation
You need to accommodate for DPI scale. Your screen is probably set to 200%. So, if you have an Image control with dimensions 100x100, you actually need 200x200 pixel image to fill it. If you give it a 100x100 bitmap source, then it will stretch it to fit. This is why you get pixelation.
So, to solve your problem, either disable any stretching on the image OR resize it by the size of the source reduced by the DPI scale. That is, set the image width and height to 50x50 for a 100x100 pixel image at 200% DPI scale.
DPI scale can be obtained on the UI thread using:
var dpiScale = DisplayInformation.GetForCurrentView().LogicalDpi / 96f;
My code can make DropShadow for TextBlock but cannot create it for image if I create image from screen shot. Using normal image (Image source has been set already) problem do not occur.
I believe that problem must be in image format (somehow) I get from screen shot. Any way to convert SoftwareBitmap to other format or what to do?
(To test with TextBlock just replace Image with TextBlock in the first snippet)
Code to copy any element on the screen to image
public static async Task<Image> GetScreenShotFromElement(FrameworkElement TargetFrameworkElement)
{
Image RenderedImage = new Image();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(TargetFrameworkElement);
RenderedImage.Source = renderTargetBitmap;
return RenderedImage;
}
Code to make drop shadow
public static void CreateDropShadowForImage(Image SOURCE,Grid SHADOWHERE)
{
Visual SOURCE_Visual = ElementCompositionPreview.GetElementVisual(SOURCE);
Compositor SOURCE_compositor = SOURCE_Visual.Compositor;
DropShadow DROP_SHADOW = SOURCE_compositor.CreateDropShadow();
DROP_SHADOW.Mask = SOURCE.GetAlphaMask();
DROP_SHADOW.Offset = new Vector3(10, 10, 0);
SpriteVisual SPRITE_VISUAL = SOURCE_compositor.CreateSpriteVisual();
SPRITE_VISUAL.Size = SOURCE.RenderSize.ToVector2();
SPRITE_VISUAL.Shadow = DROP_SHADOW;
ElementCompositionPreview.SetElementChildVisual(SHADOWHERE, SPRITE_VISUAL);
// Make sure size of shadow host and shadow visual always stay in sync
var bindSizeAnimation = SOURCE_compositor.CreateExpressionAnimation("hostVisual.Size");
bindSizeAnimation.SetReferenceParameter("hostVisual", SOURCE_Visual);
SPRITE_VISUAL.StartAnimation("Size", bindSizeAnimation);
}
Actually i am wondering to erase an image to transparency. like i have an image on page background and another image above that. Now i want that if i erase above image by finger then lower image should be appear, simply means to say image will become transparent.i'm doing something like this but its not meet my requirements.
Your suggestions are Welcome :)
private void Canvas_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
currentPoint = e.GetPosition(this.canvas);
//Initialize line according to currentpoint position.
Line line = new Line()
{
X1 = currentPoint.X,
Y1 = currentPoint.Y,
X2 = oldPoint.X,
Y2 =oldPoint.Y
};
line.StrokeDashCap = PenLineCap.Round;
line.StrokeEndLineCap = PenLineCap.Round;
line.StrokeLineJoin = PenLineJoin.Round;
line.StrokeThickness = 20;
line.Stroke = new SolidColorBrush(Colors.Black) ;
////////////////////////////////
//Set color & thickness of line.
//Line add in canvas children to draw image & assign oldpoint.
this.canvas.Children.Add(line);
oldPoint = currentPoint;
}
You can do it with 3 different ways:
Using opaque overlay and UIElement.Clip property. But you need to deal with Geometry. And I'm afraid it will be very CPU cost.
Using WriteableBitmap and changing an alpha channel of image. You can do it using WriteableBitmapEx.
Using opaque overlay and UIElement.OpacityMask property. I think it's the best way to accomplish that, as you're not limited to use bitmap (so you can place any XAML control below overlay) as in the second way.
I have written the following chunk of code that prints my ListBox perfectly when being sent to a physical printer, however when trying to send it to the XPS printer driver or using the XpsDocumentWriter class (I assume they use the same code under the hood) I receive the following exception:
System.ArgumentException was unhandled by user code
Message=Width and Height must be non-negative.
Source=ReachFramework
StackTrace:
at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The exception obviously points to an item not having a correct width/height however I have debugged the code when sending it to the different printers (physical and XPS driver) and I haven't been able to find any differences.
Below is how I create the visual to send to the printer:
private ScrollViewer GeneratePrintableView()
{
ScrollViewer scrollView = new ScrollViewer();
Grid grid = new Grid { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
grid.RowDefinitions.Add(new RowDefinition());
grid.RowDefinitions[1].Height = new GridLength(0, GridUnitType.Auto);
// Add the title and icon to the top
VisualBrush titleClone = new VisualBrush(this.TitleBar);
var titleRectangle = new Rectangle { Fill = titleClone, Width = this.TitleBar.ActualWidth, Height = this.TitleBar.ActualHeight };
grid.Children.Add(titleRectangle);
Grid.SetRow(titleRectangle, 0);
this.myListBox.Width = this.myListBox.ActualWidth;
this.myListBox.Height = this.myListBox.ActualHeight;
VisualBrush clone = new VisualBrush(this.myListBox) { Stretch = Stretch.None, AutoLayoutContent = true };
var rectangle = new Rectangle { Fill = clone, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
Border border = new Border { Background = Brushes.White, Width = this.myListBox.ActualWidth, Height = this.myListBox.ActualHeight };
border.Child = rectangle;
grid.Children.Add(border);
Grid.SetRow(border, 1);
scrollView.Width = this.myListBox.ActualWidth;
scrollView.Height = this.myListBox.ActualHeight;
scrollView.Content = grid;
scrollView.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
return scrollView;
}
Here is the GetPage override in my DocumentPaginator implementation:
public override DocumentPage GetPage(int pageNumber)
{
Page page = new Page();
double z = 0.0;
this.grid = new Grid();
this.grid.RowDefinitions.Add(new RowDefinition());
this.grid.RowDefinitions[0].Height = new GridLength(0, GridUnitType.Auto);
this.grid.Children.Add(this.printViewer);
Grid.SetRow(this.printViewer, 0);
//Adjusting the vertical scroll offset depending on the page number
if (pageNumber + 1 == 1) //if First Page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (pageNumber + 1 == _verticalPageCount) //if Last Page
{
if (this.printViewer.ScrollableHeight == 0) //If printing only single page and the contents fits only on one page
{
this.printViewer.ScrollToVerticalOffset(0);
this.printViewer.UpdateLayout();
}
else if (this.printViewer.ScrollableHeight <= this.printViewer.Height) //If scrollconentheight is less or equal than scrollheight
{
this.printViewer.Height = this.printViewer.ScrollableHeight;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
else //if the scrollcontentheight is greater than scrollheight then set the scrollviewer height to be the remainder between scrollcontentheight and scrollheight
{
this.printViewer.Height = (this.printViewer.ScrollableHeight % this.printViewer.Height) + 5;
this.printViewer.ScrollToEnd();
this.printViewer.UpdateLayout();
}
}
else //Other Pages
{
z = z + this.printViewer.Height;
this.printViewer.ScrollToVerticalOffset(z);
this.printViewer.UpdateLayout();
}
page.Content = this.grid; //put the grid into the page
page.Arrange(new Rect(this.originalMargin.Left, this.originalMargin.Top, this.ContentSize.Width, this.ContentSize.Height));
page.UpdateLayout();
return new DocumentPage(page);
}
Interestingly if I change the Fill of rectangle to a Brush instead of clone then I do not receive the exception and the outputted file is the correct size.
I have spent over a day trying to debug why this isn't working and I am hoping that someone out there has either seen a similar issue or is able to point out any mistakes I am making.
Thanks for any responses.
I had to give up finding a solution with VisualBrush. If there is a GroupBox in the Visual for the brush I could never get it to produce a XPS file. It always fails with
System.ArgumentException was unhandled by user code Message=Width and Height must be non-negative. Source=ReachFramework StackTrace: at System.Windows.Xps.Serialization.VisualSerializer.WriteTileBrush(String element, TileBrush brush, Rect bounds)
The workaround was to clone the content that should go in the VisualBrush (Is there an easy/built-in way to get an exact copy (clone) of a XAML element?) and use that directly in a Grid instead of an VisualBrush
Have you checked the value of ActualWidth and ActualHeight of myListBox when the VisualBrush is being created? I don't know from where myListBox comes, but if it is not rendered by the time you are generating your xps document you may run into problems. You can try to manually force the control to render and see if it makes any difference.
I was unable to rectify the problem however using this link Paginated printing of WPF visuals I was able to find a suitable solution to allow printing of complicated visuals within my WPF application.
It's 2016 now and it's still not fixed. The problem is using TileBrush or any descendant type (VisualBrush in your case). If you use absolute mapping, it works, it's the relative mapping that causes the problem. Calculate the final size yourself and set Viewport to this size, ViewportUnits to Absolute. Also make sure you don't use Stretch.
I have a .png image i wish to overlay on a base image.
My overlay image contains just a red slant line. I need to get the red line overlayed on the base image at the same co-ordinate as it is in overlay image.
The problem is I do not have the co-ordinates location.
I need to find it programmatically with C#. The overlay image will always be transparent or of white background. What code to find the line co-ordinates from overlay image?
You can create new image, render background image first and then render overlay image over it. Since overlay has alpha channel and line is placed where it should be (i mean there is opaque space on top and left side of line) you do not need coordinates. Illustration code:
Image imageBackground = Image.FromFile("bitmap1.png");
Image imageOverlay = Image.FromFile("bitmap2.png");
Image img = new Bitmap(imageBackground.Width, imageBackground.Height);
using (Graphics gr = Graphics.FromImage(img))
{
gr.DrawImage(imageBackground, new Point(0, 0));
gr.DrawImage(imageOverlay, new Point(0, 0));
}
img.Save("output.png", ImageFormat.Png);
If you are using WPF, why not use a path for your overlay instead of an image if it is a simple line? This would allow it to scale to any size, and has methods for manipulating its dimensions.
If you are using Winforms, there are some similar graphics drawing capabilities you might leverage. Getting the dimensions of the image should be easy, assuming you're using a PictureBox, the following properties should suffice:
myPictureBox.Top
myPictureBox.Bottom
myPictureBox.Left
myPictureBox.Right
Similarly, for a WPF Image:
myImage.Margin
I already needed to create a blank space around an image and I used the ImageFactory library to do that.
Here is the code. I guess you are capable to figure out how to adjust to your needs.
public static Image ResizedImage(Image image, int desiredWidth, int desiredHeight)
{
Image res = (Bitmap)image.Clone();
Image resizedImage;
ImageLayer imageLayer = new ImageLayer();
try
{
if (res != null)
{
//white background
res = new Bitmap(desiredWidth, desiredHeight, res.PixelFormat);
//image to handle
using (var imgf = new ImageFactory(true))
{
imgf
.Load(image)
.Resize(new ResizeLayer(new Size(desiredWidth, desiredHeight),
ResizeMode.Max,
AnchorPosition.Center,
false));
resizedImage = (Bitmap)imgf.Image.Clone();
}
//final image
if (resizedImage != null)
{
imageLayer.Image = resizedImage;
imageLayer.Size = new Size(resizedImage.Width, resizedImage.Height);
imageLayer.Opacity = 100;
using (var imgf = new ImageFactory(true))
{
imgf
.Load(res)
.BackgroundColor(Color.White)
.Overlay(imageLayer);
res = (Bitmap)imgf.Image.Clone();
}
}
}
}
catch (Exception ex)
{
ex.Message;
}
return res;
}