512 строки
16 KiB
C#
512 строки
16 KiB
C#
//
|
|
// ImageHandler.cs
|
|
//
|
|
// Author:
|
|
// Luís Reis <luiscubal@gmail.com>
|
|
// Eric Maupin <ermau@xamarin.com>
|
|
//
|
|
// Copyright (c) 2012 Luís Reis
|
|
// Copyright (c) 2012 Xamarin, Inc.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using Xwt.Backends;
|
|
using Xwt.WPFBackend.Interop;
|
|
using SWM = System.Windows.Media;
|
|
using SWMI = System.Windows.Media.Imaging;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Xwt.WPFBackend
|
|
{
|
|
public class ImageHandler: ImageBackendHandler
|
|
{
|
|
public override object LoadFromStream (Stream stream)
|
|
{
|
|
var img = new SWMI.BitmapImage ();
|
|
img.BeginInit();
|
|
img.CacheOption = SWMI.BitmapCacheOption.OnLoad;
|
|
img.StreamSource = stream;
|
|
img.EndInit();
|
|
|
|
return LoadFromImageSource (img);
|
|
}
|
|
|
|
public static object LoadFromImageSource (ImageSource img)
|
|
{
|
|
var bmp = img as BitmapSource;
|
|
if (bmp != null && (bmp.DpiX != 96 || bmp.DpiY != 96))
|
|
return new WpfImage (ConvertBitmapTo96DPI (bmp));
|
|
|
|
return new WpfImage (img);
|
|
}
|
|
|
|
public static BitmapSource ConvertBitmapTo96DPI (BitmapSource bitmapImage)
|
|
{
|
|
double dpi = 96;
|
|
int width = bitmapImage.PixelWidth;
|
|
int height = bitmapImage.PixelHeight;
|
|
|
|
int stride = width * (bitmapImage.Format.BitsPerPixel + 7) / 8;
|
|
byte[] pixelData = new byte[stride * height];
|
|
bitmapImage.CopyPixels (pixelData, stride, 0);
|
|
|
|
return BitmapSource.Create (width, height, dpi, dpi, bitmapImage.Format, null, pixelData, stride);
|
|
}
|
|
|
|
public override object CreateCustomDrawn (ImageDrawCallback drawCallback)
|
|
{
|
|
return new WpfImage (drawCallback);
|
|
}
|
|
|
|
public override object CreateMultiResolutionImage (System.Collections.Generic.IEnumerable<object> images)
|
|
{
|
|
var refImg = (WpfImage)images.First ();
|
|
var f = refImg.Frames[0];
|
|
var frames = images.Cast<WpfImage> ().Select (img => new WpfImage.ImageFrame (img.Frames[0].ImageSource, f.Width, f.Height));
|
|
return new WpfImage (frames);
|
|
}
|
|
|
|
public override object CreateMultiSizeIcon (IEnumerable<object> images)
|
|
{
|
|
return new WpfImage (images.Cast<WpfImage> ().SelectMany (i => i.Frames));
|
|
}
|
|
|
|
public override void SaveToStream (object backend, Stream stream, Drawing.ImageFileType fileType)
|
|
{
|
|
var image = DataConverter.AsImageSource(backend) as BitmapSource;
|
|
BitmapEncoder encoder;
|
|
switch (fileType) {
|
|
case Drawing.ImageFileType.Png: encoder = new PngBitmapEncoder (); break;
|
|
case Drawing.ImageFileType.Jpeg: encoder = new JpegBitmapEncoder (); break;
|
|
case Drawing.ImageFileType.Bmp: encoder = new BmpBitmapEncoder (); break;
|
|
default: throw new NotSupportedException ("Image format not supported");
|
|
}
|
|
encoder.Frames.Add (BitmapFrame.Create (image));
|
|
encoder.Save (stream);
|
|
}
|
|
|
|
public override Drawing.Image GetStockIcon (string id)
|
|
{
|
|
var img1 = RenderStockIcon (id, NativeStockIconOptions.Small);
|
|
var img2 = RenderStockIcon (id, NativeStockIconOptions.Large);
|
|
var img3 = RenderStockIcon (id, NativeStockIconOptions.ShellSize);
|
|
var img4 = RenderStockIcon (id, default (NativeStockIconOptions));
|
|
|
|
return ApplicationContext.Toolkit.WrapImage (CreateMultiSizeIcon (new object[] { img1, img2, img3, img4 }));
|
|
}
|
|
|
|
object RenderStockIcon (string id, NativeStockIconOptions options)
|
|
{
|
|
if (Environment.OSVersion.Version.Major <= 5)
|
|
throw new NotImplementedException ();
|
|
|
|
switch (id) {
|
|
case StockIconId.Add:
|
|
using (var s = typeof (ImageHandler).Assembly.GetManifestResourceStream ("Xwt.WPF.icons.list-add.png"))
|
|
return LoadFromStream (s);
|
|
case StockIconId.Remove:
|
|
using (var s = typeof (ImageHandler).Assembly.GetManifestResourceStream ("Xwt.WPF.icons.list-remove.png"))
|
|
return LoadFromStream (s);
|
|
|
|
case StockIconId.Error:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Error, options));
|
|
case StockIconId.Information:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Info, options));
|
|
case StockIconId.OrientationLandscape:
|
|
case StockIconId.OrientationPortrait:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Help, options));
|
|
//throw new NotImplementedException();
|
|
case StockIconId.Question:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Help, options));
|
|
case StockIconId.Warning:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Warning, options));
|
|
case StockIconId.Zoom100:
|
|
case StockIconId.ZoomFit:
|
|
case StockIconId.ZoomIn:
|
|
case StockIconId.ZoomOut:
|
|
return new WpfImage (NativeMethods.GetImage (NativeStockIcon.Find, options));
|
|
|
|
default:
|
|
throw new ArgumentException ("Unknown icon id", "id");
|
|
}
|
|
}
|
|
|
|
public override Xwt.Drawing.Color GetBitmapPixel (object handle, int x, int y)
|
|
{
|
|
var wpfImage = (WpfImage)handle;
|
|
BitmapSource img = wpfImage.MainFrame as BitmapSource;
|
|
if (img == null)
|
|
throw new NotSupportedException ("Invalid image format");
|
|
if (img.Format.BitsPerPixel != 32)
|
|
throw new NotSupportedException ("Image format not supported");
|
|
|
|
wpfImage.AllocatePixelData ();
|
|
var offset = wpfImage.GetPixelOffset (x, y);
|
|
return Xwt.Drawing.Color.FromBytes (wpfImage.PixelData[offset + 2], wpfImage.PixelData[offset + 1], wpfImage.PixelData[offset], wpfImage.PixelData[offset + 3]);
|
|
}
|
|
|
|
public override void SetBitmapPixel (object handle, int x, int y, Drawing.Color color)
|
|
{
|
|
var wpfImage = (WpfImage)handle;
|
|
var img = (BitmapSource)wpfImage.MainFrame;
|
|
if (img == null)
|
|
throw new NotSupportedException ("Invalid image format");
|
|
if (img.Format.BitsPerPixel != 32)
|
|
throw new NotSupportedException ("Image format not supported");
|
|
|
|
var bitmapImage = img as WriteableBitmap;
|
|
|
|
if (bitmapImage == null) {
|
|
bitmapImage = new WriteableBitmap (img);
|
|
((WpfImage)handle).MainFrame = bitmapImage;
|
|
}
|
|
|
|
wpfImage.AllocatePixelData ();
|
|
var offset = wpfImage.GetPixelOffset (x, y);
|
|
wpfImage.PixelData[offset] = (byte)(color.Blue * 255);
|
|
wpfImage.PixelData[offset + 1] = (byte)(color.Green * 255);
|
|
wpfImage.PixelData[offset + 2] = (byte)(color.Red * 255);
|
|
wpfImage.PixelData[offset + 3] = (byte)(color.Alpha * 255);
|
|
|
|
bitmapImage.Lock ();
|
|
bitmapImage.WritePixels (new Int32Rect (x, y, 1, 1), wpfImage.PixelData, wpfImage.Stride, offset);
|
|
bitmapImage.Unlock ();
|
|
}
|
|
|
|
private static double WidthToDPI (SWMI.BitmapSource img, double pixels)
|
|
{
|
|
return pixels * 96 / img.DpiX;
|
|
}
|
|
|
|
private static double HeightToDPI (SWMI.BitmapSource img, double pixels)
|
|
{
|
|
return pixels * 96 / img.DpiY;
|
|
}
|
|
|
|
public static double WidthToPixels (ImageSource img)
|
|
{
|
|
if (img is SWMI.BitmapSource) {
|
|
var bs = (BitmapSource)img;
|
|
return (bs.DpiX * bs.Width) / 96;
|
|
}
|
|
else
|
|
return img.Width;
|
|
}
|
|
|
|
public static double HeightToPixels (ImageSource img)
|
|
{
|
|
if (img is SWMI.BitmapSource) {
|
|
var bs = (BitmapSource)img;
|
|
return (bs.DpiY * bs.Height) / 96;
|
|
}
|
|
else
|
|
return img.Height;
|
|
}
|
|
|
|
public override object ConvertToBitmap (object img, double width, double height, double scaleFactor, Xwt.Drawing.ImageFormat format)
|
|
{
|
|
var wpfImage = (WpfImage)img;
|
|
return new WpfImage (wpfImage.GetBestFrame (ApplicationContext, scaleFactor, width, height, true));
|
|
}
|
|
|
|
public override bool HasMultipleSizes (object handle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool IsBitmap (object handle)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override Size GetSize (object handle)
|
|
{
|
|
var source = (WpfImage) handle;
|
|
return source.Size;
|
|
}
|
|
|
|
public override object CopyBitmap (object handle)
|
|
{
|
|
return new WpfImage (((SWMI.BitmapSource)DataConverter.AsImageSource (handle)).Clone ());
|
|
}
|
|
|
|
public override object CropBitmap(object handle, int srcX, int srcY, int w, int h)
|
|
{
|
|
var oldImg = (SWMI.BitmapSource)DataConverter.AsImageSource (handle);
|
|
var bmp = new CroppedBitmap (oldImg, new Int32Rect (srcX, srcY, w, h));
|
|
return new WpfImage (bmp);
|
|
}
|
|
|
|
public override void CopyBitmapArea (object srcHandle, int srcX, int srcY, int width, int height, object destHandle, int destX, int destY)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
}
|
|
|
|
class WpfImage
|
|
{
|
|
public class ImageFrame
|
|
{
|
|
public ImageSource ImageSource { get; set; }
|
|
public double Width { get; private set; }
|
|
public double Height { get; private set; }
|
|
public double Scale { get; set; }
|
|
public ImageFrame (ImageSource pix)
|
|
{
|
|
ImageSource = pix;
|
|
Width = pix.Width;
|
|
Height = pix.Height;
|
|
Scale = 1;
|
|
}
|
|
public ImageFrame (ImageSource pix, double width, double height)
|
|
{
|
|
ImageSource = pix;
|
|
Width = width;
|
|
Height = height;
|
|
Scale = pix.Width / width;
|
|
}
|
|
public void Dispose ()
|
|
{
|
|
}
|
|
}
|
|
|
|
ImageDrawCallback drawCallback;
|
|
|
|
public byte[] PixelData;
|
|
public int Stride;
|
|
public bool PixelWritePending;
|
|
|
|
ImageFrame[] frames;
|
|
|
|
public WpfImage (ImageSource image)
|
|
{
|
|
if (image is BitmapFrame)
|
|
this.frames = ((BitmapFrame)image).Decoder.Frames.Select (f => new ImageFrame (f)).ToArray ();
|
|
else
|
|
this.frames = new ImageFrame[] { new ImageFrame (image) };
|
|
}
|
|
|
|
public WpfImage (IEnumerable<ImageSource> images)
|
|
{
|
|
this.frames = images.Select (f => new ImageFrame (f)).ToArray ();
|
|
}
|
|
|
|
public WpfImage (IEnumerable<ImageFrame> frames)
|
|
{
|
|
this.frames = frames.ToArray ();
|
|
}
|
|
|
|
public WpfImage (ImageDrawCallback drawCallback)
|
|
{
|
|
this.drawCallback = drawCallback;
|
|
}
|
|
|
|
public ImageFrame[] Frames
|
|
{
|
|
get { return frames; }
|
|
}
|
|
|
|
public ImageSource MainFrame
|
|
{
|
|
get { return frames[0].ImageSource; }
|
|
set { frames[0].ImageSource = value; }
|
|
}
|
|
|
|
public bool HasMultipleSizes
|
|
{
|
|
get { return frames != null && frames.Length> 1 || drawCallback != null; }
|
|
}
|
|
|
|
public Size Size {
|
|
get { return frames.Length > 0 ? new Size (frames[0].Width, frames[0].Height) : Size.Zero; }
|
|
}
|
|
|
|
public int GetPixelOffset (int x, int y)
|
|
{
|
|
if (frames.Length == 0)
|
|
throw new NotSupportedException ();
|
|
BitmapSource img = frames[0].ImageSource as BitmapSource;
|
|
return y * Stride + x * ((img.Format.BitsPerPixel + 7) / 8);
|
|
}
|
|
|
|
public void AllocatePixelData ()
|
|
{
|
|
if (PixelData == null) {
|
|
BitmapSource img = frames[0].ImageSource as BitmapSource;
|
|
var height = (int) ImageHandler.HeightToPixels (img);
|
|
var width = (int) ImageHandler.WidthToPixels (img);
|
|
Stride = (width * img.Format.BitsPerPixel + 7) / 8;
|
|
PixelData = new byte[height * Stride];
|
|
img.CopyPixels (PixelData, Stride, 0);
|
|
}
|
|
}
|
|
|
|
ImageSource FindFrame (double width, double height, double scaleFactor)
|
|
{
|
|
if (frames == null)
|
|
return null;
|
|
if (frames.Length == 1)
|
|
return frames[0].ImageSource;
|
|
|
|
ImageSource best = null;
|
|
int bestSizeMatch = 0;
|
|
double bestResolutionMatch = 0;
|
|
|
|
foreach (var f in frames) {
|
|
int sizeMatch;
|
|
if (f.Width == width && f.Height == height) {
|
|
if (f.Scale == scaleFactor)
|
|
return f.ImageSource; // Exact match
|
|
sizeMatch = 2; // Exact size
|
|
}
|
|
else if (f.Width >= width && f.Height >= height)
|
|
sizeMatch = 1; // Bigger size
|
|
else
|
|
sizeMatch = 0; // Smaller size
|
|
|
|
var resolutionMatch = ((double)f.ImageSource.Width * (double)f.ImageSource.Height) / ((double)width * (double)height * scaleFactor);
|
|
|
|
if (best == null ||
|
|
(bestResolutionMatch < 1 && resolutionMatch > bestResolutionMatch) ||
|
|
(bestResolutionMatch >= 1 && resolutionMatch >= 1 && resolutionMatch <= bestResolutionMatch && (sizeMatch >= bestSizeMatch))) {
|
|
best = f.ImageSource;
|
|
bestSizeMatch = sizeMatch;
|
|
bestResolutionMatch = resolutionMatch;
|
|
}
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
public ImageSource GetBestFrame (ApplicationContext actx, Visual w, double width, double height, bool forceExactSize)
|
|
{
|
|
return GetBestFrame (actx, w.GetScaleFactor (), width, height, forceExactSize);
|
|
}
|
|
|
|
public ImageSource GetBestFrame (ApplicationContext actx, double scaleFactor, double width, double height, bool forceExactSize)
|
|
{
|
|
var f = FindFrame (width, height, scaleFactor);
|
|
if (f == null || (forceExactSize && (Math.Abs (f.Width - width * scaleFactor) > 0.01 || Math.Abs (f.Height - height * scaleFactor) > 0.01)))
|
|
return RenderFrame (actx, scaleFactor, width, height);
|
|
else
|
|
return f;
|
|
}
|
|
|
|
ImageSource RenderFrame (ApplicationContext actx, double scaleFactor, double width, double height)
|
|
{
|
|
ImageDescription idesc = new ImageDescription () {
|
|
Alpha = 1,
|
|
Size = new Size (width, height)
|
|
};
|
|
SWM.DrawingVisual visual = new SWM.DrawingVisual ();
|
|
using (SWM.DrawingContext ctx = visual.RenderOpen ()) {
|
|
ctx.PushTransform (new ScaleTransform (scaleFactor, scaleFactor));
|
|
Draw (actx, ctx, scaleFactor, 0, 0, idesc);
|
|
ctx.Pop ();
|
|
}
|
|
|
|
SWMI.RenderTargetBitmap bmp = new SWMI.RenderTargetBitmap ((int)(width * scaleFactor), (int)(height * scaleFactor), 96, 96, PixelFormats.Pbgra32);
|
|
bmp.Render (visual);
|
|
|
|
var f = new ImageFrame (bmp, width, height);
|
|
AddFrame (f);
|
|
return bmp;
|
|
}
|
|
|
|
void AddFrame (ImageFrame frame)
|
|
{
|
|
if (frames == null)
|
|
frames = new ImageFrame[] { frame };
|
|
else {
|
|
Array.Resize (ref frames, frames.Length + 1);
|
|
frames[frames.Length - 1] = frame;
|
|
}
|
|
}
|
|
|
|
public void Draw (ApplicationContext actx, SWM.DrawingContext dc, double scaleFactor, double x, double y, ImageDescription idesc)
|
|
{
|
|
if (drawCallback != null) {
|
|
DrawingContext c = new DrawingContext (dc, scaleFactor);
|
|
actx.InvokeUserCode (delegate {
|
|
drawCallback (c, new Rectangle (x, y, idesc.Size.Width, idesc.Size.Height));
|
|
});
|
|
}
|
|
else {
|
|
if (idesc.Alpha < 1)
|
|
dc.PushOpacity (idesc.Alpha);
|
|
|
|
var f = GetBestFrame (actx, scaleFactor, idesc.Size.Width, idesc.Size.Height, false);
|
|
dc.DrawImage (f, new Rect (x, y, idesc.Size.Width, idesc.Size.Height));
|
|
|
|
if (idesc.Alpha < 1)
|
|
dc.Pop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
public class ImageBox : System.Windows.Controls.Canvas
|
|
{
|
|
ApplicationContext actx;
|
|
|
|
public static readonly DependencyProperty ImageSourceProperty =
|
|
DependencyProperty.Register ("ImageSource", typeof (ImageDescription), typeof (ImageBox), new FrameworkPropertyMetadata (ImageDescription.Null) { AffectsMeasure = true, AffectsRender = true });
|
|
|
|
public ImageBox ()
|
|
{
|
|
this.actx = ToolkitEngineBackend.GetToolkitBackend<WPFEngine> ().ApplicationContext;
|
|
}
|
|
|
|
public ImageBox (ApplicationContext actx)
|
|
{
|
|
this.actx = actx;
|
|
}
|
|
|
|
protected override void OnRender (System.Windows.Media.DrawingContext dc)
|
|
{
|
|
var image = ImageSource;
|
|
if (!image.IsNull) {
|
|
var x = (RenderSize.Width - image.Size.Width) / 2;
|
|
var y = (RenderSize.Height - image.Size.Height) / 2;
|
|
((WpfImage)image.Backend).Draw (actx, dc, this.GetScaleFactor (), x, y, image);
|
|
}
|
|
}
|
|
|
|
public ImageDescription ImageSource
|
|
{
|
|
get { return (ImageDescription)this.GetValue (ImageSourceProperty); }
|
|
set { SetValue (ImageSourceProperty, value); }
|
|
}
|
|
|
|
protected override System.Windows.Size MeasureOverride (System.Windows.Size constraint)
|
|
{
|
|
var image = ImageSource;
|
|
if (!image.IsNull)
|
|
return new System.Windows.Size (image.Size.Width, image.Size.Height);
|
|
else
|
|
return new System.Windows.Size (0, 0);
|
|
}
|
|
}
|
|
}
|