I'm using SlimDX to create a texture consisting of 13046 different DataRectangles. Here's my code. It's breaking on the Texture2D constructor with "E_INVALIDARG: An invalid parameter was passed to the returning function (-2147024809)." inParms is just a struct containing handle to a Panel.
public Renderer(Parameters inParms, ref DataRectangle[] inShapes)
{
Texture2DDescription description = new Texture2DDescription()
{
Width = 500,
Height = 500,
MipLevels = 1,
ArraySize = inShapes.Length,
Format = Format.R32G32B32_Float,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
CpuAccessFlags = CpuAccessFlags.None,
OptionFlags = ResourceOptionFlags.None
};
SwapChainDescription chainDescription = new SwapChainDescription()
{
BufferCount = 1,
IsWindowed = true,
Usage = Usage.RenderTargetOutput,
ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
SampleDescription = new SampleDescription(1, 0),
Flags = SwapChainFlags.None,
OutputHandle = inParms.Handle,
SwapEffect = SwapEffect.Discard
};
Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, chainDescription, out mDevice, out mSwapChain);
Texture2D texture = new Texture2D(Device, description, inShapes);
}
R32G32B32_Float (and pretty much every 3 channels format) is not supported for render target usage.
So you have different options:
Use R32G32B32A32_Float instead (means that you will need to change your Data Rectangles as you add an extra channel).
Remove the BindFlags.RenderTarget flag. If you only want to read this resource, this flag is not needed. As you provide initial data, you can also change Usage to ResourceUsage.Immutable as well.
Additionally, to check if a Format is supported for a specific resource usage, you can use the following snippet:
public bool IsFormatSupported(Device dev, FormatSupport usage, Format format)
{
FormatSupport support = dev.CheckFormatSupport(format);
return (support | usage) == support;
}
Related
I'm trying to get the geometry of an element
i.e. a BufferGeometry object corresponding to an expressId I have (not through picking).
Basically I'm asking how to traverse the IFC model and export each object as a separate OBJ.
I'll note I have reverse engineered code to achieve that for some version of the package, but it uses undocumented functionality, so naturally it broke in later versions (the code also colors the geometry according to the material's color so I don't need an mtl):
Don't copy this code it won't work
Object.values(bimModel.ifcManager.state.models[bimModel.modelID].items).forEach(type => {
Object.entries(type.geometries).forEach(([id, geometry]) => {
const properties = bimModel.getItemProperties(Number(id))
const numVertices = geometry.getAttribute('position').count
const color = type.material.color.toArray().map(x => x * 255)
const vertexColors = new Uint8Array(Array.from({ length: numVertices }, () => color).flat())
geometry.setAttribute('color', new BufferAttribute(vertexColors, 3, true))
})
})
This is exactly what we do to export models to glTF. The basic workflow is:
Decide what IFC categories you would like to export.
Get all the items of each category.
Reconstruct the mesh for each item.
Export the mesh using the Three.js exporter of your choice.
Let's see a basic example to get all the meshes from the walls. The process is not as straightforward as having each IFC item as a separate mesh, but that's the price for having the draw calls at minimum (otherwise, a browser wouldn't stand even medium-sized IFC files):
import { IFCWALLSTANDARDCASE } from 'web-ifc';
async function getAllWallMeshes() {
// Get all the IDs of the walls
const wallsIDs = manager.getAllItemsOfType(0, IFCWALL, false);
const meshes = [];
const customID = 'temp-gltf-subset';
for (const wallID of wallsIDs) {
const coordinates = [];
const expressIDs = [];
const newIndices = [];
const alreadySaved = new Map();
// Get the subset for the wall
const subset = viewer.IFC.loader.ifcManager.createSubset({
ids: [wallID],
modelID,
removePrevious: true,
customID
});
// Subsets have their own index, but share the BufferAttributes
// with the original geometry, so we need to rebuild a new
// geometry with this index
const positionAttr = subset.geometry.attributes.position;
const expressIDAttr = subset.geometry.attributes.expressID;
const newGroups = subset.geometry.groups
.filter((group) => group.count !== 0);
const newMaterials = [];
const prevMaterials = subset.material;
let newMaterialIndex = 0;
newGroups.forEach((group) => {
newMaterials.push(prevMaterials[group.materialIndex]);
group.materialIndex = newMaterialIndex++;
});
let newIndex = 0;
for (let i = 0; i < subset.geometry.index.count; i++) {
const index = subset.geometry.index.array[i];
if (!alreadySaved.has(index)) {
coordinates.push(positionAttr.array[3 * index]);
coordinates.push(positionAttr.array[3 * index + 1]);
coordinates.push(positionAttr.array[3 * index + 2]);
expressIDs.push(expressIDAttr.getX(index));
alreadySaved.set(index, newIndex++);
}
const saved = alreadySaved.get(index);
newIndices.push(saved);
}
const geometryToExport = new BufferGeometry();
const newVerticesAttr = new BufferAttribute(Float32Array.from(coordinates), 3);
const newExpressIDAttr = new BufferAttribute(Uint32Array.from(expressIDs), 1);
geometryToExport.setAttribute('position', newVerticesAttr);
geometryToExport.setAttribute('expressID', newExpressIDAttr);
geometryToExport.setIndex(newIndices);
geometryToExport.groups = newGroups;
geometryToExport.computeVertexNormals();
const mesh = new Mesh(geometryToExport, newMaterials);
meshes.push(mesh);
}
viewer.IFC.loader.ifcManager.removeSubset(modelID, undefined, customID);
return meshes;
}
Landsat and MODIS products both have their advantages. Landsat with its high spatial resolution and MODIS with its high temporal resolution. I've read a lot about downloading the files and fuse them locally with algorithms like STARFM in Python for example. Is there a way to fuse both collections directly in Google Earth Engine to save computational time?
There is Google earth engine code in GitHub. The link is below:
https://github.com/MacGallagher/GEE_STARFM_FUSION
The code I copied is below:
/**** Start of imports. If edited, may not auto-convert in the playground. ****/
var l8 = ee.ImageCollection("LANDSAT/LC08/C01/T1_SR"),
modis = ee.ImageCollection("MODIS/006/MOD09GQ"),
geometry4 = /* color: #d63000 */ee.Geometry.Polygon(
[[[-116.004638671875, 42.809506838324204],
[-115.59539794921875, 42.78129125156276],
[-115.499267578125, 43.004647127794435],
[-115.499267578125, 43.28920196020127],
[-116.00189208984375, 43.35114690203121],
[-116.19415283203125, 43.07691312608711]]]),
geometry = /* color: #98ff00 */ee.Geometry.Polygon(
[[[-115.93391418457031, 43.09346209534859],
[-115.80276489257812, 43.095718426590174],
[-115.80276489257812, 43.161366298103566],
[-115.9332275390625, 43.16086543618915]]]);
/***** End of imports. If edited, may not auto-convert in the playground. *****/
// based on script by Faye Peters, University of Louisville
// modified by Megan Gallagher, Boise State University
// for code inquiries, or adjustments please email megangallagher#u.boisestate.edu
// code exists for:
// landsat 5,7, and 8 NDVI merging with MODIS Terra daily
// MODIS Terra and Sentinel-2
// Landsat pre tier and modis daily terra - works with base R code
// Landsat 8 and Sentinel-2
////////////////////////////////////////Variables//////////////////////////////////////////////
// import Landsat 8 and MODIS daily terra surface reflectance 250m
var region = geometry; //region you want to export
var bounds = geometry4 //outer bounds of image to catch boundary effect, make larger than region
var date_begin = '2016-03-02' //start date of data collection, must be a landsat image date
var date_end ='2016-10-13' // end date of data collection, must be the day AFTER last landsat image
var csv_title = '2016_Dates_BOP' //title of csv output
var ls_title = '2016_landsat' // title of landsat tif file
var mod_title = '2016_mod' // title of modis tif file
/////////////////////////////////////////Code////////////////////////////////////////////////////////////////////////
var starfm = function(modis, l8){
//Preliminary filtering of MODIS and Landsat
var filt_mod = modis.filterDate(date_begin, date_end);
var filt_l8 = l8.filterDate(date_begin, date_end)
.filterBounds(bounds);
//add bounds to subset area
var subset_bounds = bounds.transform('EPSG:4326', 30).bounds().getInfo();
// get rid of bad pixels but keep metadata date information
var removeBadObservations = function(image){
var valid_data_mask = ee.Image(image).select('pixel_qa').bitwiseAnd(2).neq(0);
var numberBandsHaveData =
image.mask().reduce(ee.Reducer.sum());
var allOrNoBandsHaveData =
numberBandsHaveData.eq(0).or(numberBandsHaveData.gte(9));
var allBandsHaveData = allOrNoBandsHaveData;
//Make sure no band is just under zero
var allBandsGT = image.reduce(ee.Reducer.min()).gt(-0.001);
var result = ee.Image(image).mask(image.mask().and(valid_data_mask).and(allBandsHaveData).and(allBandsGT));
return result.copyProperties(ee.Image(image),['system:time_start']);
};
//NDVI functions for Landsat and MODIS
var getNDVI_mod = function(image){
return image
.addBands(image.normalizedDifference(['sur_refl_b02','sur_refl_b01']).multiply(10000).rename('NDVI'));};
var getNDVI_l8= function(image){
var ndvi = ee.Image(image).normalizedDifference(['B5','B4']);
return ndvi.copyProperties(ee.Image(image),['system:time_start']);
};
var filt2_l8= filt_l8.filterBounds(subset_bounds).aside(print).map(removeBadObservations).map(getNDVI_l8);
// update mask for different areas
var subset_mask = ee.Image().byte().paint(geometry4, "id").add(1);
var filtered_modis = filt_mod.filterBounds(subset_bounds).aside(print).map(getNDVI_mod);
// Pull out the date:
var extract_modis_date = function(row) {
var d = ee.Date(row.get('system:time_start'));
var d2 = ee.Date.fromYMD(d.get('year'), d.get('month'), d.get('day'));
var result = ee.Feature(null, {'date': d2});
result = result.set({'date': d2});
return result;
};
var getQABits = function(image, start, end, newName) {
//// Compute the bits we need to extract.
var pattern = 0;
for (var i = start; i <= end; i++) {
pattern += Math.pow(2, i);
}
//// Return a single band image of the extracted QA bits, giving the band a new name.
return image.select([0], [newName])
.bitwiseAnd(pattern)
.rightShift(start); };
print(filtered_modis);
filtered_modis = filtered_modis.map(function(image){
var quality = getQABits(image.select(2), 4, 5, 'QAMask');
quality = quality.eq(3).not();
return image.clip(subset_bounds).mask(image.mask().multiply(subset_mask).multiply(quality));
});
filtered_modis = filtered_modis.select('NDVI'); //Take only NDVI band
var modis_multiband = ee.Image(filtered_modis.filterDate(date_begin, date_end).iterate( function(x, modis_multiband) {
return ee.Image(modis_multiband).addBands(ee.Image(x));
}, filtered_modis.first()));
var dates_modis = filtered_modis.map(extract_modis_date);
print(dates_modis.getInfo());
var filt2_l8_ndvi = filt2_l8.map(function(image) {
return ee.Image(image)
.clip(subset_bounds)
.mask(ee.Image(image).mask().multiply(subset_mask));
});
//use this to choose the range of days, and check for overlap
var day_expand = 1;
var reduceLandsatNDVI = function(MODISdate) {
MODISdate = ee.Date(MODISdate.get('date'));
var ndvi_subset = ee.ImageCollection(filt2_l8_ndvi).filterDate( MODISdate,
MODISdate.advance(day_expand, 'day') );
ndvi_subset = ndvi_subset.map(function (image) {
var diff = MODISdate.difference(ee.Date(ee.Image(image).get('system:time_start')), 'day').abs();
return ee.Image(image).set('diff', diff);
});
ndvi_subset = ndvi_subset.sort('diff');
var ndvi_first = ndvi_subset.reduce('first');
var ndvi_mean = ndvi_subset.reduce('mean');
return ee.Algorithms.If(
ndvi_first.bandNames(),
ndvi_first.eq(0).multiply(ndvi_mean).add(ndvi_first),
ee.Image(0)
);
};
var extract_landsat_date = function(MODISdate) {
MODISdate = ee.Date(MODISdate.get('date'));
var ndvi_subset =
ee.ImageCollection(filt2_l8_ndvi).filterDate( MODISdate,
MODISdate.advance(day_expand, 'day') );
ndvi_subset = ndvi_subset.map(function (image) {
var diff = MODISdate.difference(ee.Date(ee.Image(image).get('system:time_start')), 'day').abs();
return ee.Image(image).set('diff', diff);
});
ndvi_subset = ndvi_subset.sort('diff');
var d = ndvi_subset.aggregate_first('system:time_start');
var count = ndvi_subset.aggregate_count('system:time_start');
d = ee.Algorithms.If(
ee.Number(count).gt(0),
ee.Date(d),
ee.Date('1971-01-01')
);
d = ee.Date(d);
var d2 = ee.Date.fromYMD(d.get('year'), d.get('month'),
d.get('day'));
var result = ee.Feature(null, {'LSdate': d2, 'MODISdate':
MODISdate, 'CountLSScenes': count});
result = result.set({'LSdate': d2, 'MODISdate': MODISdate,
'CountLSScenes': count});
return result;
};
var ls_collection = dates_modis.map(reduceLandsatNDVI);
print(ls_collection,'ls collection');
var dates_landsat = dates_modis.map(extract_landsat_date);
Export.table(dates_landsat, csv_title);
var ls_multiband = ls_collection.iterate( function(x,
ls_multiband)
{ return ee.Image(ls_multiband).addBands(ee.Image(x));
}, ls_collection.first());
ls_multiband = ee.Image(ls_multiband).multiply(10000).int16();
ls_multiband = ls_multiband.mask(ls_multiband.mask().multiply(ls_multiband.neq(0)));
//remove repeated first layer
var ls_multiband2=ee.Image(ls_multiband.slice(1))
var modis_multiband2=ee.Image(modis_multiband.slice(1))
Export.image.toDrive({
image: modis_multiband2,
description: mod_title,
crs:'EPSG:4326 ',
region:region,
scale:30
});
Export.image.toDrive({
image: ls_multiband2,
description: ls_title,
crs:'EPSG:4326',
region:region,
scale:30
});
return 'Done!'
}
var running = starfm(modis,l8)
print(running)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
My main dataset has a minPeriod of one day, "DD".
I then add a new dataset to compare to the main dataset. This new dataset has a minPeriod of one min, "mm".
However, even after changing chart.categoryAxesSettings.minPeriod to equal mm, the new dataset does not appear.
But when I use a main dataset that also has a minPeriod='mm', the new compared dataset does appear.
So, I'm wondering how I can use a main dataset with a minPeriod='DD' and compare to it a new dataset with minPeriod='mm'.
Please let me know if you need more info.
Thanks! Find code below.
chart = new AmCharts.AmStockChart();
//this is my main dataset
//the data in it has a minPeriod of one day
dataSet = new AmCharts.DataSet();
dataSet.title = 'Price';
dataSet.dataProvider = chartData;
dataSet.fieldMappings = [{fromField:"Open", toField:"open"}, {fromField:"High", toField:"high"}, {fromField:"Low", toField:"low"}, {fromField:"Close", toField:"close"}, {fromField:"Volume", toField:"volume"}, {fromField:"orderPrice", toField:"orderPrice"}];
dataSet.categoryField = "date";
chart.dataSets = [dataSet];
categoryAxesSettings = new AmCharts.CategoryAxesSettings();
categoryAxesSettings.minPeriod = "DD";
categoryAxesSettings.maxSeries = 750;
chart.categoryAxesSettings = categoryAxesSettings;
//main panel
pricePanel = new AmCharts.StockPanel();
pricePanel.recalculateToPercents = "never";
//create priceGraph
var priceGraph = new AmCharts.StockGraph();
priceGraph.valueField = "closeField";
priceGraph.type = "ohlc";
priceGraph.title = "Price";
priceGraph.openField = 'open';
priceGraph.highField = 'high';
priceGraph.lowField = 'low';
priceGraph.closeField = 'close';
pricePanel.addStockGraph(priceGraph);
//display chart
chart.write("chartdiv");
//create new dataset where data has minPeriod = 'mm'
var orderDataset = new AmCharts.DataSet();
orderDataset.title = 'orderSet';
orderDataset.dataProvider = orderGraphData;
orderDataset.fieldMappings = [{fromField:"orderPrice", toField:"orderPrice"}];
orderDataset.categoryField = "date";
orderDataset.compared = true;
chart.dataSets.push(orderDataset);
//create priceGraph
var orderGraph = new AmCharts.StockGraph();
orderGraph.comparable = true;
orderGraph.compareField = 'orderPrice';
orderGraph.compareGraph = {
"type": "step",
"bullet": "round",
"lineThickness": 2,
"bulletBorderColor": "#FFFFFF",
"bulletBorderAlpha": 1,
"bulletBorderThickness": 3
};
pricePanel.addStockGraph(orderGraph);
//change minPeriod to mm
chart.categoryAxesSettings.minPeriod = "mm";
chart.validateData();
How can I concatenate disparate chunks and add them to a paragraph, the paragraph to a cell, then the cell to a table using iTextSharp (in generating a PDF file)?
I am able to get to a certain "place" in my PDF file generation, so that it looks like so (the right side of the page is blank, as it should be):
This is the code I'm using for that:
using (var ms = new MemoryStream())
{
using (var doc = new Document(PageSize.A4, 50, 50, 25, 25))
{
//Create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(doc, ms))
{
//Open the document for writing
doc.Open();
var courierBold11Font = FontFactory.GetFont(FontFactory.COURIER_BOLD, 11, BaseColor.BLACK);
var docTitle = new Paragraph("Mark Twain", courierBold11Font);
doc.Add(docTitle);
var timesRoman9Font = FontFactory.GetFont("Times Roman", 9, BaseColor.BLACK);
var subTitle = new Paragraph("Roughing It", timesRoman9Font);
doc.Add(subTitle);
var courier9RedFont = FontFactory.GetFont("Courier", 9, BaseColor.RED);
var importantNotice = new Paragraph("'All down but nine; set 'em up on the other alley, pard' - Scotty Briggs", courier9RedFont);
importantNotice.Leading = 0;
importantNotice.MultipliedLeading = 0.9F; // reduce the width between lines in the paragraph with these two settings
PdfPTable table = new PdfPTable(1);
PdfPCell cellImportantNote = new PdfPCell(importantNotice);
cellImportantNote.BorderWidth = PdfPCell.NO_BORDER;
table.WidthPercentage = 50;
table.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cellImportantNote);
doc.Add(table);
doc.Close();
}
var bytes = ms.ToArray();
String PDFTestOutputFileName = String.Format("iTextSharp_{0}.pdf", DateTime.Now.ToShortTimeString());
PDFTestOutputFileName = PDFTestOutputFileName.Replace(":", "_");
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), PDFTestOutputFileName);
File.WriteAllBytes(testFile, bytes);
MessageBox.Show(String.Format("{0} written", PDFTestOutputFileName));
}
}
However, I need to break up the red text so that part of it is bolded, parts of it are anchor tags/hrefs, etc.
I thought I could do it this way:
var courier9RedBoldFont = FontFactory.GetFont(FontFactory.COURIER_BOLD, 9, BaseColor.RED);
// Build up chunkified version of "important notice"
Chunk boldpart = new Chunk("All down but nine - set 'em up on the other alley, pard", courier9RedBoldFont);
Chunk attribution = new Chunk("Scotty Briggs", courier9RedFont);
PdfPTable tbl = new PdfPTable(1);
tbl.WidthPercentage = 50;
tbl.HorizontalAlignment = Element.ALIGN_LEFT;
var par = new Paragraph();
par.Chunks.Add(boldpart);
par.Chunks.Add(attribution );
PdfPCell chunky = new PdfPCell(par);
chunky.BorderWidth = PdfPCell.NO_BORDER;
tbl.AddCell(chunky);
doc.Add(tbl);
...but that's not adding anything at all to the PDF file, but why not? Doesn't a cell take a paragraph, and cannot a paragraph be comprised of Chunks?
Instead of para.Chunks.Add() just use par.Add(); The Chunks that are returned from Paragraph actually come from the base class Phrase. If you look at the code for that property you'll see that the collection returned is actually a temporary collection created on the fly so it is effectively read-only.
I am trying to add cloud image to album cover if the cover is loaded from internet. I am trying to do this in Background Audio agent and I think I almost got it. The problem is that I have black image in tile. Few times when testing I got cover image with my cloud image in it but mostly I get black image (and sometimes black image with cloud in it).
Can anyone help me find the problem? Thanks
private void UpdateAppTile()
{
var apptile = ShellTile.ActiveTiles.First();
if (apptile != null && _playList != null && _playList.Any())
{
var track = _playList[currentTrackNumber];
var size = 360;
Uri coverUrl;
if (track.AlbumArt.OriginalString.StartsWith("http"))
{
BitmapImage img = null;
using (AutoResetEvent are = new AutoResetEvent(false))
{
string filename = Path.GetFileNameWithoutExtension(track.AlbumArt.OriginalString);
var urlToNewCover = String.Format("http://.../{0}/{1}", filename, size);
coverUrl = new Uri(urlToNewCover, UriKind.Absolute);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
img = new BitmapImage(coverUrl);
are.Set();
});
are.WaitOne();
var wbmp = CreateTileImageWithCloud(img);
SaveTileImage(wbmp, "/shared/shellcontent/test.jpg");
coverUrl = new Uri("isostore:/shared/shellcontent/test.jpg", UriKind.RelativeOrAbsolute);
}
}
else
{
var coverId = track.Tag.Split(',')[1];
var urlToNewCover = String.Format("http://.../{0}/{1}", coverId, size);
coverUrl = new Uri(urlToNewCover, UriKind.Absolute);
}
var appTileData = new FlipTileData
{
BackgroundImage = coverUrl,
WideBackgroundImage = coverUrl,
...
}
apptile.Update(appTileData);
}
}
public static BitmapImage LoadBitmap(string iFilename)
{
Uri imgUri = new Uri(iFilename, UriKind.Relative);
StreamResourceInfo imageResource = Application.GetResourceStream(imgUri);
BitmapImage image = new BitmapImage();
image.SetSource(imageResource.Stream);
return image;
}
private void SaveTileImage(WriteableBitmap wbmp, string filename)
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (store.FileExists(filename))
store.DeleteFile(filename);
var stream = store.OpenFile(filename, FileMode.OpenOrCreate);
wbmp.SaveJpeg(stream, wbmp.PixelWidth, wbmp.PixelHeight, 100, 100);
stream.Close();
}
}
private WriteableBitmap CreateTileImageWithCloud(BitmapImage img)
{
Image image = null;
WriteableBitmap wbmp = null;
using (AutoResetEvent are = new AutoResetEvent(false))
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
image = new Image { Source = img };
Canvas.SetLeft(image, 0);
Canvas.SetTop(image, 0);
var cloud = new BitmapImage(new Uri("Assets/Images/Other/Cloud_no.png", UriKind.Relative));
var cloudImg = new Image { Source = cloud };
Canvas.SetLeft(cloudImg, 125);
Canvas.SetTop(cloudImg, 10);
var canvas = new Canvas
{
Height = 176,
Width = 176
};
canvas.Children.Add(image);
canvas.Children.Add(cloudImg);
wbmp = new WriteableBitmap(176, 176);
wbmp.Render(canvas, null);
wbmp.Invalidate();
are.Set();
});
are.WaitOne();
}
return wbmp;
}
Edit
I found little pattern in which this is working and in which not. When application is running and I called this twice (in TrackReady and SkipNext) then I very often get cover image with cloud. When I am running just background agent (without running app) I get always black image. And often first UpdateAppTile call is just black image and second it's black image with cloud. That black color is default canvas background so I guess I have problems with delay when loading cover image from url. But I am not sure how in my case use ImageOpened event and if it help.
I think that you should call Measure and Arrange after adding elements to canvas and before rendering canvas (as with other UIElements):
canvas.Measure( new Size( Width, Height ) );
canvas.Arrange( new Rect( 0, 0, Width, Height ) );