From release 1.55.0 SkiaSharp has the support for reading SVG files.
The package has been release few days ago (10 Nov. 2016) and I couldn't find enough documentation on how to use it.
The following packages are required:
SkiaSharp 1.55.0
SkiaSharp Views & Layers 1.55.0
SkiaSharp.Svg 1.55.0-beta1
The first question is what's the best way to load an SKSvg in Xamarin.Android?
Here two possible solutions to start working with SkiaSharp that are working for me:
Loading SVG from Asset folder (or subfolder):
public SKSvg LoadAnSvgFromAssets(Context ctx, string assetSvgFileLoc)
{
var assets = ctx.Assets;
var svg = new SKSvg();
using (var stream = new StreamReader(assets.Open(assetSvgFileLoc)))
{
svg.Load(stream.BaseStream);
return svg;
}
}
where "assetSvgFileLoc" is the svgFilename.svg to load, including (if it's the case) the path inside Asset folder (e.g. "subf1/subf2/mysvg.svg").
Loading SVG as RAW Resource
public SKSvg LoadAnSvgFromResources(Context ctx, string svgName))
{
var resId = ctx.Resources.GetIdentifier(svgName, "raw", ctx.PackageName);
var svg = new SKSvg();
using (var stream = ctx.Resources.OpenRawResource(resId))
{
svg.Load(stream);
return svg;
}
}
In this case the file is inside the Resources subfolder "raw" and the "svgName" is the filename of our svg without extension.
To complete the accepted answer, here is how to load the SKSvg from a URL.
SkiaSharp.Extended.Svg.SKSvg svg = new SkiaSharp.Extended.Svg.SKSvg();
using (WebClient client = new WebClient())
{
svg.Load(new MemoryStream(client.DownloadData(new Uri(ResourceUrl))));
}
I am using this code ,it works great. I can have the SVG in PCL , ios or Android folder
Just remember to include the Assembly in path
var assembly = this.GetType().GetTypeInfo().Assembly;
using (var s = assembly.GetManifestResourceStream("MyAssembly.Core.Hold.svg"))
{
if (s != null)
{
svg = new SKSvg();
svg.Load(s);
using (SKPaint paint = new SKPaint())
{
canvas.DrawPicture(svg.Picture, paint);
}
}
}
Related
I have a Xamarin Forms app where I've included a font file called Roboto-Regular.ttf in the Assets folder of the Android project. Its Build Action is set to AndroidAsset.
Using the SixLabors.Fonts NuGet package, I'm trying to load this font to use it for watermarking.
However, trying to install the font using the asset stream, an exception is thrown saying:
System.NotSupportedException: Specified method is not supported.
var fonts = new FontCollection();
FontFamily fontFamily;
using (var fontStream = Assets.Open("Roboto-Regular.ttf"))
{
fontFamily = fonts.Install(fontStream); // Fails with "method not supported"
}
return fontFamily;
Any ideas what might be causing this, or if there is a better way to load fonts for use with the SixLabors.ImageSharp package?
Edit: I tried the suggestion below by SushiHangover, but it yields the same result:
There is are two Assets.Open methods and one provides an accessMode (C# Access enum flag set):
using (var fontStream = Assets.Open("Roboto-Regular.ttf", Android.Content.Res.Access.Random))
{
fontFamily = fonts.Install(fontStream);
}
re: https://developer.android.com/reference/android/content/res/AssetManager.html#open(java.lang.String,%20int)
public enum Access
{
[IntDefinition (null, JniField = "android/content/res/AssetManager.ACCESS_BUFFER")]
Buffer = 3,
[IntDefinition (null, JniField = "android/content/res/AssetManager.ACCESS_RANDOM")]
Random = 1,
[IntDefinition (null, JniField = "android/content/res/AssetManager.ACCESS_STREAMING")]
Streaming,
[IntDefinition (null, JniField = "android/content/res/AssetManager.ACCESS_UNKNOWN")]
Unknown = 0
}
Seems the underlying Stream did not have Length or Position properties (which explains the exception), so for now I resorted to converting to a seekable MemoryStream instead:
using (var assetStreamReader = new StreamReader(Assets.Open("Roboto-Regular.ttf"))
{
using (var ms = new MemoryStream())
{
assetStreamReader.BaseStream.CopyTo(ms);
ms.Position = 0;
var fontFamily = new FontCollection().Install(ms);
}
}
Looking at the FontReader implementation, the error now makes even more sense: https://github.com/SixLabors/Fonts/blob/master/src/SixLabors.Fonts/FontReader.cs
However, I'm not sure why Assets doesn't return a seekable stream?
Working with Weex 0.16 I generated the default "WeexDemo" project for Web, iOS and Android, and the default index files works without issue.
I'm trying to figure out the folder location where I can put image files and other assets. I changed the "logoUrl" from an http url to a relative path ('bg.png')
Serving on the browser over 8080 port web root is the project root folder, so if I copy bg.png to "dist" folder I can see it when logoUrl is "/dist/bg.png"
On the iOS project "/dist/bg.png" does not work, since the root seems to be the "dist" folder itself, if I change to logoUrl "bg.png", it works.
On the Android project, both "/dist/bg.png" or "bg.png" fail. I can see in the folder "/platforms/android/app/src/main/assets/dist" the bg.png file has been copied but I don't seem to able to access it.
Is there any solution where I can use the same folder to drop the assets and have it accessible for all devices?
Do as follows :
In your application class :
WXSDKEngine.registerComponent("yourImage", YourImage.class);
create class YourImage.java and it should look like this :
public class YourImage extends WXComponent<ImageView> {
public YourImage(WXSDKInstance instance, WXDomObject dom, WXVContainer parent) {
super(instance, dom, parent);
}
#Override
protected ImageView initComponentHostView(#NonNull Context context) {
ImageView imageView = new ImageView(context);
return imageView;
}
#WXComponentProp(name = Constants.Name.SRC)
public void setSrc(String src) {
if (src == null) {
return;
}
if(null != getHostView()) {
Picasso.with(WXEnvironment.getApplication()).load(src).into(getHostView());
}
}
}
In your vue/js file
<yourImage class="image" :src="url"> </yourImage>
In your WeexUIFragment/Activity : while loading page data through map, add one key and value as follows:
HashMap<String, Object> map = new HashMap<>();
map.add("image_url", "android local asset url")
map.put("PAGENAME", "");
mWXSDKInstance = new WXSDKInstance(YourCurrentActivity.this);
mWXSDKInstance.registerRenderListener(YourCurrentActivity.this);
mWXSDKInstance.render(pageName, weexJSUrl, map, null, WXRenderStrategy.APPEND_ASYNC);
In JS file in script/data block to retrieve asset local image url:
const url = _.get(weex,'config.image_url')
If you need more help: You can look into Component Extend section in below link :
https://weex.incubator.apache.org/guide/extend-android.html
I am building a Xamarin.Forms project with a PCL, iOS and Android project. One of my requirements is that I have to read a JSON file stored in the platform project (iOS/Android) from the PCL.
How can I do this please? I can't find a solution for this problem. :(
Thank you very much,
Nikolai
If the file that you want to read is embedded in the platform project assembly you can use the following code:
var assembly = typeof(MyPage).GetTypeInfo().Assembly;
Stream stream = assembly.GetManifestResourceStream("WorkingWithFiles.PCLTextResource.txt");
string text = "";
using (var reader = new System.IO.StreamReader (stream)) {
text = reader.ReadToEnd ();
}
Make sure that you replace WorkingWithFiles with namespace of your project and PCLTextResource.txt with name of the file.
Check Xamarin documentation at Loading Files Embedded as Resources for more details
If on the other hand you want to create and read/write files at runtime you can use PCLStorage library:
public async Task PCLStorageSample()
{
IFolder rootFolder = FileSystem.Current.LocalStorage;
IFolder folder = await rootFolder.CreateFolderAsync("MySubFolder",
CreationCollisionOption.OpenIfExists);
IFile file = await folder.CreateFileAsync("answer.txt",
CreationCollisionOption.ReplaceExisting);
await file.WriteAllTextAsync("42");
}
I got it working by using an IoC container.
In my iOS project I have created a ConfigAccess class:
public class ConfigAccess : IConfigAccess
{
public string ReadConfigAsString()
{
return System.IO.File.ReadAllText("config.json");
}
}
I also had to add the following line to my AppDelegate.cs
SimpleIoc.Default.Register<IConfigAccess, ConfigAccess>();
And in my PCL I am simply asking for a ConfigAccess object during runtime:
var configAccess = SimpleIoc.Default.GetInstance<IConfigAccess>();
var test = configAccess.ReadConfigAsString();
I need to read a text stream by using StreamReader from file on android platform. File is about 100k lines, so even editor is getting stuck if i try to load it all to TextAsset or if i use WWW.
I simply need to read that file line by line without loading it all to a string. Then i'll do a tree generation from the lines that i got from the file. (But probably that part doesn't matter, i just need help on file reading part.)
I'm giving the code that i wrote down below. It works perfectly on editor, but fails on android.
I would be glad if anyone tell me, what am i missing.
(ps. english is not my native and this is my first question on the site. so sorry for the any mistakes that i may have done.)
private bool Load(string fileName)
{
try
{
string line;
string path = Application.streamingAssetsPath +"/";
StreamReader theReader = new StreamReader(path + fileName +".txt", Encoding.UTF8);
using (theReader)
{
{
line = theReader.ReadLine();
linesRead++;
if (line != null)
{
tree.AddWord(line);
}
}
while (line != null);
theReader.Close();
return true;
}
}
catch (IOException e)
{
Debug.Log("{0}\n" + e.Message);
exception = e.Message;
return false;
}
}
You can't use Application.streamingAssetsPath as a path on Android because streaming assets are stored within the JAR file with the application.
From http://docs.unity3d.com/Manual/StreamingAssets.html:
Note that on Android, the files are contained within a compressed .jar
file (which is essentially the same format as standard zip-compressed
files). This means that if you do not use Unity’s WWW class to
retrieve the file then you will need to use additional software to see
inside the .jar archive and obtain the file.
Use WWW like this in a coroutine:
WWW data = new WWW(Application.streamingAssetsPath + "/" + fileName);
yield return data;
if(string.IsNullOrEmpty(data.error))
{
content = data.text;
}
Or, if you really want to keep it simple (and your file is only a few 100k, stick it in a resource folder:
TextAsset txt = (TextAsset)Resources.Load(fileName, typeof(TextAsset));
string content = txt.text;
I use saveToFile method to save screenshot, and I check CCLOG, it shows /data/data/com.xxx.xxx/files/xxx.png
However, when I use Java to get this file, it says "No such file or directory"....
What can I do?
I had the same problem. Apparently, cocos2dx never creates the writable path directory so the screenshot doesn't get saved.
My solution was to, instead of calling CCRenderTexture::saveToFile, copy its implementation and manually save the file to a different path:
std::string MyClass::takeScreenshot(CCScene* scene) {
cocos2d::CCSize screenSize = cocos2d::CCDirector::sharedDirector()->getWinSize();
cocos2d::CCRenderTexture* tex = cocos2d::CCRenderTexture::create(screenSize.width, screenSize.height);
tex->setPosition(ccp(screenSize.width/2, screenSize.height/2));
//use a different dir than cocos default writable path
std::string filename = "/sdcard/Android/data/screenshot.png";
tex->begin();
scene->getParent()->visit();
cocos2d::CCImage* img = tex->newCCImage(true);
img->saveToFile(filename.c_str(), true);
CC_SAFE_DELETE(img);
tex->end();
return filename;
}
I also had the same problem. When I try renderTexture->saveToFile method, I can't submit path to java. No matter, I used getWritablePath() or set the path directly "/sdcard/Android/data/com.xxx.xxx/snapshot.jpg".
I try also to solve this problem on the advice of Facundo Olano. But CCImage compose only solid black image.
Finally I combine this two methods. First, I make an image by renderTexture->saveToFile:
void MyScene::pressFaceBook()
{
...
RenderTexture* renderTexture = RenderTexture::create(sharedImage->getBoundingBox().size.width, sharedImage->getBoundingBox().size.height, Texture2D::PixelFormat::RGBA8888);
renderTexture->begin();
sharedImage->visit();
renderTexture->end();
renderTexture->saveToFile("snapshot.jpg", Image::Format::JPG);
const std::string fullpath = FileUtils::getInstance()->getWritablePath() + "snapshot.jpg";
sharedImage->setVisible(false);
this->runAction(Sequence::create(DelayTime::create(2.0f),
CallFunc::create(std::bind(&MyScene::shareFaceBook, this, fullpath)),
NULL));
}
And then I use initWithImageFile with the transferred file name:
void MyScene::shareFaceBook(const std::string& outputFile)
{
const std::string joutputFile = "/sdcard/Android/data/com.xxx.xxx/snapshot.jpg";
cocos2d::Image* img = new Image;
img->initWithImageFile(fullpath);
img->saveToFile(joutputFile);
CC_SAFE_DELETE(img);
singleton->shareFaceBook(joutputFile);
}
A delay of 2 seconds is needed to capture the screenshot.
Of course, instead com.xxx.xxx you must substitute your app name.