Jump to content
Paint.NET 5.1 is now available! ×

Recommended Posts

Posted (edited)

So I tried using Pinta's OraFormat.cs to make it use Paint.NET's datatypes and ways... lots of copy paste.

onload atleast is without errors what VS seems to think but ofcourse it doesn't work. OnSave is hopeless. All the commented stuff in OnSave is from OraFormat.cs

So to those who are better at C#, help? I used Simon's template.

// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>

using System;
using System.IO;
using System.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;

namespace OpenRaster_Filetype
{
public class OraFileType : FileType
	{
	private const int ThumbMaxSize = 256;

	public OraFileType() : base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
		{
		}

	protected override Document OnLoad(Stream input)
		{
		ZipFile file = new ZipFile(input);
		XmlDocument stackXml = new XmlDocument();
		stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

		XmlElement imageElement = stackXml.DocumentElement;
		int width = int.Parse(imageElement.GetAttribute("w"));
		int height = int.Parse(imageElement.GetAttribute("h"));

		Document doc = new Document(width, height);

		XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
		XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

		if (layerElements.Count == 0)
			throw new XmlException("No layers found in OpenRaster file");

		for (int i = 0; i < layerElements.Count; i++)
			{
			XmlElement layerElement = (XmlElement)layerElements[i];
			int x = int.Parse(GetAttribute(layerElement, "x", "0"));
			int y = int.Parse(GetAttribute(layerElement, "y", "0"));
			string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", i));

			ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));
			Stream s = file.GetInputStream(zf);

			// var myLayer = new BitmapLayer(x, y); // is this right?
			var myLayer = Layer.CreateBackgroundLayer(x, y); // or is this better?
			myLayer.Name = name;
			myLayer.Opacity = (byte) (255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"))); // double to byte? wtf am I doing... O_o there is a better way... right? >_>
			myLayer.Visible = bool.Parse(GetAttribute(layerElement, "opacity", "1"));

			myLayer.Surface.CopyFromGdipBitmap(new Bitmap(s)); // does this make sense?

			doc.Layers.Insert(0, myLayer);
			}

		return doc;
		}

	protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
		{
		ZipOutputStream stream = new ZipOutputStream(output);
		ZipEntry mimetype = new ZipEntry("mimetype");
		mimetype.CompressionMethod = CompressionMethod.Stored;
		stream.PutNextEntry(mimetype);

		byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");
		stream.Write(databytes, 0, databytes.Length);

		for (int i = 0; i < input.Layers.Count; i++)
			{


			stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
			stream.Write(buf, 0, buf.Length);
			}

    		/* // OraFormat.cs
		for (int i = 0; i < document.Layers.Count; i++)
			{
			Pixbuf pb = document.Layers[i].Surface.ToPixbuf();
			byte[] buf = pb.SaveToBuffer("png");
			(pb as IDisposable).Dispose();

			stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
			stream.Write(buf, 0, buf.Length);
			}
		*/

		stream.PutNextEntry(new ZipEntry("stack.xml"));
		databytes = GetLayerXmlData(input.Layers);
		stream.Write(databytes, 0, databytes.Length);

		/* // OraFormat.cs
		ImageSurface flattened = document.GetFlattenedImage();
		Pixbuf flattenedPb = flattened.ToPixbuf();
		Size newSize = GetThumbDimensions(flattenedPb.Width, flattenedPb.Height);
		Pixbuf thumb = flattenedPb.ScaleSimple(newSize.Width, newSize.Height, InterpType.Bilinear);

		stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
		databytes = thumb.SaveToBuffer("png");
		stream.Write(databytes, 0, databytes.Length);

		(flattened as IDisposable).Dispose();
		(flattenedPb as IDisposable).Dispose();
		(thumb as IDisposable).Dispose();
		*/

		stream.Close();
		}

	private byte[] GetLayerXmlData(LayerList layers) // OraFormat.cs - minor changes, no idea if still works
		{
		MemoryStream ms = new MemoryStream();
		XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
		writer.Formatting = Formatting.Indented;

		writer.WriteStartElement("image");
		writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString());
		writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString());

		writer.WriteStartElement("stack");
		writer.WriteAttributeString("opacity", "1");
		writer.WriteAttributeString("name", "root");

    		// ORA stores layers top to bottom
		for (int i = layers.Count - 1; i >= 0; i--)
			{
			writer.WriteStartElement("layer");
			writer.WriteAttributeString("opacity", layers.GetAt(i).Visible ? "0" : string.Format("{0:0.00}", (double) (layers.GetAt(i).Opacity) / 255.0)); // byte to double? wtf am I doing... O_o
			writer.WriteAttributeString("name", layers.GetAt(i).Name);
			writer.WriteAttributeString("src", "data/layer" + i.ToString() + ".png");
			writer.WriteEndElement();
			}

		writer.WriteEndElement(); // stack
		writer.WriteEndElement(); // image

		writer.Close();
		return ms.ToArray();
		}

	private Size GetThumbDimensions(int width, int height) // OraFormat.cs
		{
		if (width <= ThumbMaxSize && height <= ThumbMaxSize)
			return new Size(width, height);

		if (width > height)
			return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
		else
			return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
		}

	private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
		{
		string ret = element.GetAttribute(attribute);
		return string.IsNullOrEmpty(ret) ? defValue : ret;
		}
	}

public class MyFileTypeFactory : IFileTypeFactory
	{
	public FileType[] GetFileTypeInstances()
		{
		return new FileType[] { new OraFileType() };
		}
	}
}

Edited by Zagna

sig2024.jpg.4c3dd6a1ed919373afa78c73a19ed629.jpg

Posted
// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>
//
// Copyright (c) 2010 Maia Kozheva <sikon@ubuntu.com>
//
// 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.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;
using System.Drawing.Imaging;

namespace OpenRaster_Filetype
{
public class OraFileType : FileType
{
private const int ThumbMaxSize = 256;

public OraFileType()
: base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
{
}

/// <summary>
/// Gets the bitmap from the ora layer.
/// </summary>
/// <param name="xofs">The x offset of the layer image.</param>
/// <param name="yofs">The y offset of the layer image.</param>
/// <param name="inStream">The input stream containing the layer image.</param>
/// <param name="baseWidth">The width of the base document.</param>
/// <param name="baseHeight">The height of the base document.</param>
private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
{
Bitmap image = null;

using (Bitmap layer = new Bitmap(baseWidth, baseHeight))
{
using (Bitmap bmp = new Bitmap(inStream))
{
BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width , bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

int bpp = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; 

for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
byte *dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * 4);
byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

dst[0] = src[0]; // B
dst[1] = src[1]; // G
dst[2] = src[2]; // R

if (bpp == 4)
{
dst[3] = src[3]; // A
}
}
}

bmp.UnlockBits(bmpData);
layer.UnlockBits(layerData);
}
image = (Bitmap)layer.Clone();
}

return image;
}

protected override Document OnLoad(Stream input)
{
using (ZipFile file = new ZipFile(input))
{

XmlDocument stackXml = new XmlDocument();
stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

XmlElement imageElement = stackXml.DocumentElement;
int width = int.Parse(imageElement.GetAttribute("w"));
int height = int.Parse(imageElement.GetAttribute("h"));

Document doc = new Document(width, height);

XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

if (layerElements.Count == 0)
throw new FormatException("No layers found in OpenRaster file");
int layerCount = layerElements.Count - 1;

for (int i = layerCount; i >= 0; i--) // The last layer in the list is the background so load in reverse
{
XmlElement layerElement = (XmlElement)layerElements[i];
int x = int.Parse(GetAttribute(layerElement, "x", "0")); // the x offset within the layer
int y = int.Parse(GetAttribute(layerElement, "y", "0")); // the y offset within the layer

int layerNum = layerCount - i;

string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", layerNum));

ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));

using (Stream s = file.GetInputStream(zf))
{
using (Bitmap bmp = GetBitmapFromOraLayer(x, y, s, width, height))
{
BitmapLayer myLayer = null;
if (i == layerCount) // load the background layer first
{
myLayer = Layer.CreateBackgroundLayer(width, height); 
}
else
{
myLayer = new BitmapLayer(width, height); 
}

myLayer.Name = name;
myLayer.Opacity = (byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"))); // double to byte? wtf am I doing... O_o there is a better way... right? >_>
myLayer.Visible = (GetAttribute(layerElement, "visibility", "visible") == "visible"); // newer ora files have this

myLayer.Surface.CopyFromGdipBitmap(bmp, false); // does this make sense?

myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", x, y));

doc.Layers.Insert(layerNum, myLayer);
} 
}

}
return doc;
}

}

protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
{
using (ZipOutputStream stream = new ZipOutputStream(output))
{
ZipEntry mimetype = new ZipEntry("mimetype");
mimetype.CompressionMethod = CompressionMethod.Stored;
stream.PutNextEntry(mimetype);

byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");
stream.Write(databytes, 0, databytes.Length);

for (int i = 0; i < input.Layers.Count; i++)
{
BitmapLayer layer = (BitmapLayer)input.Layers[i];

byte[] buf = null;
using (MemoryStream ms = new MemoryStream())
{
layer.Surface.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
buf = ms.ToArray();
}
stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
stream.Write(buf, 0, buf.Length);
}

/*// OraFormat.cs
for (int i = 0; i < document.Layers.Count; i++)
{
Pixbuf pb = document.Layers[i].Surface.ToPixbuf();
byte[] buf = pb.SaveToBuffer("png");
(pb as IDisposable).Dispose();

stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
stream.Write(buf, 0, buf.Length);
}
*/

stream.PutNextEntry(new ZipEntry("stack.xml"));
databytes = GetLayerXmlData(input.Layers);
stream.Write(databytes, 0, databytes.Length);


using (Surface flat = new Surface(input.Width, input.Height))
{
input.Flatten(flat);
Size thumbSize = GetThumbDimensions(input.Width, input.Height);

Surface scale = new Surface(thumbSize);
scale.FitSurface(ResamplingAlgorithm.SuperSampling, flat);

using (MemoryStream ms = new MemoryStream())
{
scale.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
databytes = ms.ToArray();
}
scale.Dispose();
}
stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
stream.Write(databytes, 0, databytes.Length);

/* // OraFormat.cs
ImageSurface flattened = document.GetFlattenedImage();
Pixbuf flattenedPb = flattened.ToPixbuf();
Size newSize = GetThumbDimensions(flattenedPb.Width, flattenedPb.Height);
Pixbuf thumb = flattenedPb.ScaleSimple(newSize.Width, newSize.Height, InterpType.Bilinear);

stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
databytes = thumb.SaveToBuffer("png");
stream.Write(databytes, 0, databytes.Length);

(flattened as IDisposable).Dispose();
(flattenedPb as IDisposable).Dispose();
(thumb as IDisposable).Dispose();
*/
}
}

private byte[] GetLayerXmlData(LayerList layers) // OraFormat.cs - minor changes, no idea if still works
{
byte[] buf = null;

using(MemoryStream ms = new MemoryStream())
{
XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
writer.Formatting = Formatting.Indented;

writer.WriteStartElement("image");
writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString());
writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString());

writer.WriteStartElement("stack");
writer.WriteAttributeString("opacity", "1");
writer.WriteAttributeString("name", "root");


// ORA stores layers top to bottom
for (int i = layers.Count - 1; i >= 0; i--)
{
string[] xyPos = null;

string val = ((BitmapLayer)layers[i]).Metadata.GetUserValue("oraPos");
if (!string.IsNullOrEmpty(val))
{
xyPos = new string[2];
xyPos = val.Split(new char[] { ',' });
}



writer.WriteStartElement("layer");
writer.WriteAttributeString("opacity", string.Format("{0:0.00}", (double)(layers.GetAt(i).Opacity) / 255.0)); // byte to double? wtf am I doing... O_o
writer.WriteAttributeString("name", layers.GetAt(i).Name);
writer.WriteAttributeString("src", "data/layer" + i.ToString() + ".png");
writer.WriteAttributeString("visible", layers.GetAt(i).Visible ? "visible" : "hidden");

if (xyPos != null)
{
writer.WriteAttributeString("x", xyPos[0]);
writer.WriteAttributeString("y", xyPos[1]);
}

writer.WriteEndElement();
}

writer.WriteEndElement(); // stack
writer.WriteEndElement(); // image

writer.Close(); 

buf = ms.ToArray();
}

return buf;
}

private Size GetThumbDimensions(int width, int height) // OraFormat.cs
{
if (width <= ThumbMaxSize && height <= ThumbMaxSize)
return new Size(width, height);

if (width > height)
return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
else
return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
}

private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
{
string ret = element.GetAttribute(attribute);
return string.IsNullOrEmpty(ret) ? defValue : ret;
}
}

public class MyFileTypeFactory : IFileTypeFactory
{
public FileType[] GetFileTypeInstances()
{
return new FileType[] { new OraFileType() };
}
}
}

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Posted

According to Wikipedia, at least 6 programs have support for it. So maybe it might reach some level of usage. And it seems to have potential for growth.

With null54's code, I got it to work :D


// myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"))));

myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture)));

and


// writer.WriteAttributeString("opacity", string.Format("{0:0.00}", (double)(layers.GetAt(i).Opacity) / 255.0)); // byte to double? wtf am I doing... O_o

writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre 

were the only changes required. The first fix made loading a file possible and the second change (it looks insane :D) fixed saving because it was doing '1,00' on my computer instead of 1.00

So thanks goes to null54 :D:mtdew: and :pizza: for you :D

sig2024.jpg.4c3dd6a1ed919373afa78c73a19ed629.jpg

  • 1 month later...
Posted

Thanks for your work. I've just started using MyPaint (great software!) and I really like to be able to import / export ora format with Paint.NET.

Just one thing about your code: the ora format allows each layer to have different size and uses x,y values to offset them.

In your onload method all the layers are normalized to the same size and so storing x,y offset leads to store wrong infos when saving. In fact when I tried to open / save and open again an .ora file (originally created with MyPaint) I got an error on Paint.NET and I got misalligned layers on MyPaint.

A fast fix is just to change your code this way:


// myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", x, y));
myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", 0, 0));

According to Wikipedia, at least 6 programs have support for it. So maybe it might reach some level of usage. And it seems to have potential for growth.

With null54's code, I got it to work :D


// myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"))));

myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture)));

and


// writer.WriteAttributeString("opacity", string.Format("{0:0.00}", (double)(layers.GetAt(i).Opacity) / 255.0)); // byte to double? wtf am I doing... O_o

writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre 

were the only changes required. The first fix made loading a file possible and the second change (it looks insane :D) fixed saving because it was doing '1,00' on my computer instead of 1.00

So thanks goes to null54 :D:mtdew: and :pizza: for you :D

Posted

Thanks for your work. I've just started using MyPaint (great software!) and I really like to be able to import / export ora format with Paint.NET.

Just one thing about your code: the ora format allows each layer to have different size and uses x,y values to offset them.

In your onload method all the layers are normalized to the same size and so storing x,y offset leads to store wrong infos when saving. In fact when I tried to open / save and open again an .ora file (originally created with MyPaint) I got an error on Paint.NET and I got misalligned layers on MyPaint.

A fast fix is just to change your code this way:


// myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", x, y));
myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", 0, 0));

Another issue is that the code never cropped the layer to the smallest visible rectangle, the updated code now does this.

// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>
//
// Copyright (c) 2010 Maia Kozheva <sikon@ubuntu.com>
//
// 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.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Globalization;

namespace OpenRaster_Filetype
{
public class OraFileType : FileType
{
	private const int ThumbMaxSize = 256;

	public OraFileType()
		: base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
	{
	}

	/// <summary>
	/// Gets the bitmap from the ora layer.
	/// </summary>
	/// <param name="xofs">The x offset of the layer image.</param>
	/// <param name="yofs">The y offset of the layer image.</param>
	/// <param name="inStream">The input stream containing the layer image.</param>
	/// <param name="baseWidth">The width of the base document.</param>
	/// <param name="baseHeight">The height of the base document.</param>
	private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
	{
		Bitmap image = null;

		using (Bitmap layer = new Bitmap(baseWidth, baseHeight))
		{
			using (Bitmap bmp = new Bitmap(inStream))
			{
				BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width , bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
				BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

				int bpp = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; 

				for (int y = 0; y < bmp.Height; y++)
				{
					for (int x = 0; x < bmp.Width; x++)
					{
						byte *dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * 4);
						byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

						dst[0] = src[0]; // B
						dst[1] = src[1]; // G
						dst[2] = src[2]; // R

						if (bpp == 4)
						{
							dst[3] = src[3]; // A
						}
					}
				}

				bmp.UnlockBits(bmpData);
				layer.UnlockBits(layerData);
			}
			image = (Bitmap)layer.Clone();
		}

		return image;
	}

	protected override Document OnLoad(Stream input)
	{
		using (ZipFile file = new ZipFile(input))
		{

			XmlDocument stackXml = new XmlDocument();
			stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

			XmlElement imageElement = stackXml.DocumentElement;
			int width = int.Parse(imageElement.GetAttribute("w"));
			int height = int.Parse(imageElement.GetAttribute("h"));

			Document doc = new Document(width, height);

			XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
			XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

			if (layerElements.Count == 0)
				throw new FormatException("No layers found in OpenRaster file");
			int layerCount = layerElements.Count - 1;

			for (int i = layerCount; i >= 0; i--) // The last layer in the list is the background so load in reverse
			{
				XmlElement layerElement = (XmlElement)layerElements[i];
				int x = int.Parse(GetAttribute(layerElement, "x", "0")); // the x offset within the layer
				int y = int.Parse(GetAttribute(layerElement, "y", "0")); // the y offset within the layer

				int layerNum = layerCount - i;

				string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", layerNum));

				ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));

				using (Stream s = file.GetInputStream(zf))
				{
					using (Bitmap bmp = GetBitmapFromOraLayer(x, y, s, width, height))
					{
						BitmapLayer myLayer = null;
						if (i == layerCount) // load the background layer first
						{
							myLayer = Layer.CreateBackgroundLayer(width, height); 
						}
						else
						{
							myLayer = new BitmapLayer(width, height); 
						}

						myLayer.Name = name;
	myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture))); myLayer.Visible = (GetAttribute(layerElement, "visibility", "visible") == "visible"); // newer ora files have this

						myLayer.Surface.CopyFromGdipBitmap(bmp, false); // does this make sense?

	myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", x, y));

						doc.Layers.Insert(layerNum, myLayer);
					} 
				}

			}
			return doc;
		}

	}
	// A struct to store the new x,y offsets
	private struct LayerInfo
	{
	public int x;
	public int y;

	public LayerInfo(int x, int y)
	{
	this.x = x;
	this.y = y;
	}
	}

	protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
	{
		using (ZipOutputStream stream = new ZipOutputStream(output))
		{
			ZipEntry mimetype = new ZipEntry("mimetype");
			mimetype.CompressionMethod = CompressionMethod.Stored;
			stream.PutNextEntry(mimetype);

			byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");
			stream.Write(databytes, 0, databytes.Length);

	LayerInfo[] layerInfo = new LayerInfo[input.Layers.Count];
			for (int i = 0; i < input.Layers.Count; i++)
			{
				BitmapLayer layer = (BitmapLayer)input.Layers[i];


	Rectangle bounds = layer.Surface.Bounds;


	int left = layer.Width;
	int top = layer.Height;
	int right = 0;
	int bottom = 0;
	unsafe
	{
	for (int y = 0; y < layer.Height; y++)
	{
	ColorBgra* row = layer.Surface.GetRowAddress(y);
	ColorBgra* pixel = row;

	for (int x = 0; x < layer.Width; x++)
	{
	if (pixel->A > 0)
	{

	if (x < left)
	{
	left = x;
	}
	if (x > right)
	{
	right = x;
	}
	if (y < top)
	{
	top = y;
	}
	if (y > bottom)
	{
	bottom = y;
	}

	}
	pixel++;
	}
	}
	}

	if (left < layer.Width && top < layer.Height) // is the layer not empty
	{

	bounds = new Rectangle(left, top, (right - left), (bottom - top)); // clip it to the visible rectangle

	layerInfo[i] = new LayerInfo(left, top); 
	}
	else
	{
	layerInfo[i] = new LayerInfo(0, 0);
	}




				byte[] buf = null;
	using (MemoryStream ms = new MemoryStream())
				{
					layer.Surface.CreateAliasedBitmap(bounds, true).Save(ms, ImageFormat.Png);
					buf = ms.ToArray();
				}
				stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
				stream.Write(buf, 0, buf.Length);
			}

			/*// OraFormat.cs
			for (int i = 0; i < document.Layers.Count; i++)
				{
				Pixbuf pb = document.Layers[i].Surface.ToPixbuf();
				byte[] buf = pb.SaveToBuffer("png");
				(pb as IDisposable).Dispose();

				stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
				stream.Write(buf, 0, buf.Length);
				}
			*/

			stream.PutNextEntry(new ZipEntry("stack.xml"));
			databytes = GetLayerXmlData(input.Layers, layerInfo);
			stream.Write(databytes, 0, databytes.Length);


			using (Surface flat = new Surface(input.Width, input.Height))
			{
				input.Flatten(flat);
				Size thumbSize = GetThumbDimensions(input.Width, input.Height);

				Surface scale = new Surface(thumbSize);
				scale.FitSurface(ResamplingAlgorithm.SuperSampling, flat);

				using (MemoryStream ms = new MemoryStream())
				{
					scale.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
					databytes = ms.ToArray();
				}
				scale.Dispose();
			}
			stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
			stream.Write(databytes, 0, databytes.Length);

			/* // OraFormat.cs
			ImageSurface flattened = document.GetFlattenedImage();
			Pixbuf flattenedPb = flattened.ToPixbuf();
			Size newSize = GetThumbDimensions(flattenedPb.Width, flattenedPb.Height);
			Pixbuf thumb = flattenedPb.ScaleSimple(newSize.Width, newSize.Height, InterpType.Bilinear);

			stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
			databytes = thumb.SaveToBuffer("png");
			stream.Write(databytes, 0, databytes.Length);

			(flattened as IDisposable).Dispose();
			(flattenedPb as IDisposable).Dispose();
			(thumb as IDisposable).Dispose();
			*/
		}
	}

	private byte[] GetLayerXmlData(LayerList layers, LayerInfo[] info) // OraFormat.cs - minor changes, no idea if still works
	{
		byte[] buf = null;

using(MemoryStream ms = new MemoryStream())
		{
			XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
			writer.Formatting = Formatting.Indented;

			writer.WriteStartElement("image");
			writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString());
			writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString());

			writer.WriteStartElement("stack");
			writer.WriteAttributeString("opacity", "1");
			writer.WriteAttributeString("name", "root");


			// ORA stores layers top to bottom
			for (int i = layers.Count - 1; i >= 0; i--)
			{


				writer.WriteStartElement("layer");
	writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre 
				writer.WriteAttributeString("name", layers.GetAt(i).Name);
				writer.WriteAttributeString("src", "data/layer" + i.ToString(CultureInfo.InvariantCulture) + ".png");
	writer.WriteAttributeString("visible", layers.GetAt(i).Visible ? "visible" : "hidden");

	writer.WriteAttributeString("x", info[i].x.ToString(CultureInfo.InvariantCulture));
	writer.WriteAttributeString("y", info[i].y.ToString(CultureInfo.InvariantCulture));

				writer.WriteEndElement();
			}

			writer.WriteEndElement(); // stack
			writer.WriteEndElement(); // image

			writer.Close(); 

			buf = ms.ToArray();
		}

return buf;
	}

	private Size GetThumbDimensions(int width, int height) // OraFormat.cs
	{
		if (width <= ThumbMaxSize && height <= ThumbMaxSize)
			return new Size(width, height);

		if (width > height)
			return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
		else
			return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
	}

	private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
	{
		string ret = element.GetAttribute(attribute);
		return string.IsNullOrEmpty(ret) ? defValue : ret;
	}
}

public class MyFileTypeFactory : IFileTypeFactory
{
	public FileType[] GetFileTypeInstances()
	{
		return new FileType[] { new OraFileType() };
	}
}
}

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Posted

Make sure that when you call string.Format() or ToString() that you use the overload that takes CultureInfo.InvariantCulture.

You don't want "0.8" to roundtrip as "0,8" and get the file broken when moving between systems that just aren't configured 100% the same way.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Posted

Thanks null54, my fix was just a fast workaround but you impemented the correct behaviour.

I did instead some changes to the GetBitmapFromOraLayer method, to support also 24bppRgb pixelformat. MyPaint infact use this format for background imgs.


	private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
	{
	Bitmap image = null;

	using (Bitmap bmp = new Bitmap(inStream))
	{
	PixelFormat pixelFormat = bmp.PixelFormat;

	//check for supported PixelFormat
	if(pixelFormat!= PixelFormat.Format24bppRgb && pixelFormat!= PixelFormat.Format32bppArgb)
	throw new FormatException("Unknown BitCount");

	int bpp = Bitmap.GetPixelFormatSize(pixelFormat) / 8;

	using (Bitmap layer = new Bitmap(baseWidth, baseHeight, pixelFormat))
	{

	BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, pixelFormat);
	BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, pixelFormat);

	for (int y = 0; y < bmp.Height; y++)
	{
	for (int x = 0; x < bmp.Width; x++)
	{
	byte* dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * bpp);
	byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

	dst[0] = src[0];
	dst[1] = src[1];
	dst[2] = src[2];

	if (bpp == 4)
	{
	dst[3] = src[3];
	}
	}
	}

	bmp.UnlockBits(bmpData);
	layer.UnlockBits(layerData);
	image = (Bitmap)layer.Clone();
	}
	}

	return image;
	}

NB:

MyPaint background management is pretty weird cause in the ora file it's stored either a large png file (of max layer size), either a png tile used (in mypaint) to make an infinite background. Moreover the background layer in mypaint is not an editable layer.

This logic is not preserved when loading and saving again an .ora file with Paint.net.

Posted

Thanks null54, my fix was just a fast workaround but you impemented the correct behaviour.

I did instead some changes to the GetBitmapFromOraLayer method, to support also 24bppRgb pixelformat. MyPaint infact use this format for background imgs.

NB:

MyPaint background management is pretty weird cause in the ora file it's stored either a large png file (of max layer size), either a png tile used (in mypaint) to make an infinite background. Moreover the background layer in mypaint is not an editable layer.

This logic is not preserved when loading and saving again an .ora file with Paint.net.

The only thing that prevented the GetBitmapFromOraLayer code from working was that the alpha channel was never set to 255 if the image was 24bppRgb.

The background tile should now be loaded and saved correctly.

// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>
//
// Copyright (c) 2010 Maia Kozheva <sikon@ubuntu.com>
//
// 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.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;

namespace OpenRaster_Filetype
{
public class OraFileType : FileType
{
private const int ThumbMaxSize = 256;

public OraFileType()
: base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
{
}

/// <summary>
/// Gets the bitmap from the ora layer.
/// </summary>
/// <param name="xofs">The x offset of the layer image.</param>
/// <param name="yofs">The y offset of the layer image.</param>
/// <param name="inStream">The input stream containing the layer image.</param>
/// <param name="baseWidth">The width of the base document.</param>
/// <param name="baseHeight">The height of the base document.</param>
private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
{
Bitmap image = null;

using (Bitmap layer = new Bitmap(baseWidth, baseHeight))
{
using (Bitmap bmp = new Bitmap(inStream))
{
BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width , bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

int bpp = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; 

for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
byte *dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * 4);
byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

dst[0] = src[0]; // B
dst[1] = src[1]; // G
dst[2] = src[2]; // R

if (bpp == 4) 
{
dst[3] = src[3]; // A
}
else
{
dst[3] = 255;
}
}
}

bmp.UnlockBits(bmpData);
layer.UnlockBits(layerData);
}
image = (Bitmap)layer.Clone();
}

return image;
}

protected override Document OnLoad(Stream input)
{
using (ZipFile file = new ZipFile(input))
{

XmlDocument stackXml = new XmlDocument();
stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

XmlElement imageElement = stackXml.DocumentElement;
int width = int.Parse(imageElement.GetAttribute("w"));
int height = int.Parse(imageElement.GetAttribute("h"));

Document doc = new Document(width, height);

XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

if (layerElements.Count == 0)
throw new FormatException("No layers found in OpenRaster file");
int layerCount = layerElements.Count - 1;

for (int i = layerCount; i >= 0; i--) // The last layer in the list is the background so load in reverse
{
XmlElement layerElement = (XmlElement)layerElements[i];
int x = int.Parse(GetAttribute(layerElement, "x", "0")); // the x offset within the layer
int y = int.Parse(GetAttribute(layerElement, "y", "0")); // the y offset within the layer

int layerNum = layerCount - i;

string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", layerNum));

ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));

using (Stream s = file.GetInputStream(zf))
{
using (Bitmap bmp = GetBitmapFromOraLayer(x, y, s, width, height))
{
BitmapLayer myLayer = null;
if (i == layerCount) // load the background layer first
{
myLayer = Layer.CreateBackgroundLayer(width, height); 
}
else
{
myLayer = new BitmapLayer(width, height); 
}

myLayer.Name = name;
myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture))); myLayer.Visible = (GetAttribute(layerElement, "visibility", "visible") == "visible"); // newer ora files have this

myLayer.Surface.CopyFromGdipBitmap(bmp, false); // does this make sense?

string backTile = GetAttribute(layerElement, "background_tile", string.Empty);
if (!string.IsNullOrEmpty(backTile)) 
{
ZipEntry tileZf = file.GetEntry(backTile);
byte[] tileBytes = null;
using (Stream tileStream = file.GetInputStream(tileZf))
{
tileBytes = new byte[(int)tileStream.Length];

int numBytesToRead = (int)tileStream.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
// Read may return anything from 0 to numBytesToRead.
int n = tileStream.Read(tileBytes, numBytesRead, numBytesToRead);
// The end of the file is reached.
if (n == 0)
{
break;
}
numBytesRead += n;
numBytesToRead -= n;
}

}

string tileData = Convert.ToBase64String(tileBytes);
// convert the tile image to a Base64String and then save it in the layer's MetaData.
myLayer.Metadata.SetUserValue("OraBackgroundTile", tileData); 
}


doc.Layers.Insert(layerNum, myLayer);
} 
}

}
return doc;
}

}
// A struct to store the new x,y offsets
private struct LayerInfo
{
public int x;
public int y;

public LayerInfo(int x, int y)
{
this.x = x;
this.y = y;
}
}


protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
{
using (ZipOutputStream stream = new ZipOutputStream(output))
{
ZipEntry mimetype = new ZipEntry("mimetype");
mimetype.CompressionMethod = CompressionMethod.Stored;
stream.PutNextEntry(mimetype);

byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");
stream.Write(databytes, 0, databytes.Length);

LayerInfo[] layerInfo = new LayerInfo[input.Layers.Count];
for (int i = 0; i < input.Layers.Count; i++)
{
BitmapLayer layer = (BitmapLayer)input.Layers[i];


Rectangle bounds = layer.Surface.Bounds;


int left = layer.Width;
int top = layer.Height;
int right = 0;
int bottom = 0;
unsafe
{
for (int y = 0; y < layer.Height; y++)
{
ColorBgra* row = layer.Surface.GetRowAddress(y);
ColorBgra* pixel = row;

for (int x = 0; x < layer.Width; x++)
{
if (pixel->A > 0)
{

if (x < left)
{
left = x;
}
if (x > right)
{
right = x;
}
if (y < top)
{
top = y;
}
if (y > bottom)
{
bottom = y;
}

}
pixel++;
}
}
}

if (left < layer.Width && top < layer.Height) // is the layer not empty
{

bounds = new Rectangle(left, top, (right - left), (bottom - top)); // clip it to the visible rectangle

layerInfo[i] = new LayerInfo(left, top); 
}
else
{
layerInfo[i] = new LayerInfo(0, 0);
}

string tileData = layer.Metadata.GetUserValue("OraBackgroundTile");
if (!string.IsNullOrEmpty(tileData)) // save the background_tile png if it exists
{
byte[] tileBytes = Convert.FromBase64String(tileData);
stream.PutNextEntry(new ZipEntry("data/background_tile.png"));
stream.Write(tileBytes, 0, tileBytes.Length);
}


byte[] buf = null;
using (MemoryStream ms = new MemoryStream())
{
layer.Surface.CreateAliasedBitmap(bounds, true).Save(ms, ImageFormat.Png);
buf = ms.ToArray();
}
stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString(CultureInfo.InvariantCulture) + ".png"));
stream.Write(buf, 0, buf.Length);
}

/*// OraFormat.cs
for (int i = 0; i < document.Layers.Count; i++)
{
Pixbuf pb = document.Layers[i].Surface.ToPixbuf();
byte[] buf = pb.SaveToBuffer("png");
(pb as IDisposable).Dispose();

stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
stream.Write(buf, 0, buf.Length);
}
*/

stream.PutNextEntry(new ZipEntry("stack.xml"));
databytes = GetLayerXmlData(input.Layers, layerInfo);
stream.Write(databytes, 0, databytes.Length);


using (Surface flat = new Surface(input.Width, input.Height))
{
input.Flatten(flat);
Size thumbSize = GetThumbDimensions(input.Width, input.Height);

Surface scale = new Surface(thumbSize);
scale.FitSurface(ResamplingAlgorithm.SuperSampling, flat);

using (MemoryStream ms = new MemoryStream())
{
scale.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
databytes = ms.ToArray();
}
scale.Dispose();
}
stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
stream.Write(databytes, 0, databytes.Length);

/* // OraFormat.cs
ImageSurface flattened = document.GetFlattenedImage();
Pixbuf flattenedPb = flattened.ToPixbuf();
Size newSize = GetThumbDimensions(flattenedPb.Width, flattenedPb.Height);
Pixbuf thumb = flattenedPb.ScaleSimple(newSize.Width, newSize.Height, InterpType.Bilinear);

stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
databytes = thumb.SaveToBuffer("png");
stream.Write(databytes, 0, databytes.Length);

(flattened as IDisposable).Dispose();
(flattenedPb as IDisposable).Dispose();
(thumb as IDisposable).Dispose();
*/
}
}

private byte[] GetLayerXmlData(LayerList layers, LayerInfo[] info) // OraFormat.cs - minor changes, no idea if still works
{
byte[] buf = null;

using(MemoryStream ms = new MemoryStream())
{
XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
writer.Formatting = Formatting.Indented;

writer.WriteStartElement("image");
writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString(CultureInfo.InvariantCulture));

writer.WriteStartElement("stack");
writer.WriteAttributeString("opacity", "1");
writer.WriteAttributeString("name", "root");


// ORA stores layers top to bottom
for (int i = layers.Count - 1; i >= 0; i--)
{
BitmapLayer layer = (BitmapLayer)layers[i];

writer.WriteStartElement("layer");

string backTile = layer.Metadata.GetUserValue("OraBackgroundTile");
if (!string.IsNullOrEmpty(backTile))
{
writer.WriteAttributeString("background_tile", "data/background_tile.png");
}

writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre  writer.WriteAttributeString("name", layers.GetAt(i).Name);
writer.WriteAttributeString("src", "data/layer" + i.ToString(CultureInfo.InvariantCulture) + ".png");
writer.WriteAttributeString("visible", layers.GetAt(i).Visible ? "visible" : "hidden");

writer.WriteAttributeString("x", info[i].x.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("y", info[i].y.ToString(CultureInfo.InvariantCulture));

writer.WriteEndElement();
}

writer.WriteEndElement(); // stack
writer.WriteEndElement(); // image

writer.Close(); 

buf = ms.ToArray();
}

return buf;
}

private Size GetThumbDimensions(int width, int height) // OraFormat.cs
{
if (width <= ThumbMaxSize && height <= ThumbMaxSize)
return new Size(width, height);

if (width > height)
return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
else
return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
}

private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
{
string ret = element.GetAttribute(attribute);
return string.IsNullOrEmpty(ret) ? defValue : ret;
}
}

public class MyFileTypeFactory : IFileTypeFactory
{
public FileType[] GetFileTypeInstances()
{
return new FileType[] { new OraFileType() };
}
}
}

PdnSig.png

Plugin Pack | PSFilterPdn | Content Aware Fill | G'MICPaint Shop Pro Filetype | RAW Filetype | WebP Filetype

The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait

 

Posted

Yes, you're right, but anyway there's no reason to create a (temporary) 32rgba dest bitmap when you need to blit from a 24rgb source, and then check everytime if the source is a 3 or 4 byte per pixel!

Another issue is related to the OnSave() method: I'm working on a 64 bit architecture and I discovered that by default the current implementation of OnSave code enables the Zip64 extension for the archive. That can lead problem with, for example, the default windows xp 32 bit decompression tools, but also with a 32 bit software that use the same SharpZipLib (I discovered the problem working on a .ora explorer preview 32bit shell extension).

First fix option: disable Zip 64 extension (that is needed only for ZipEntries larger than 4gb):


protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
	{
	using (ZipOutputStream stream = new ZipOutputStream(output))
	{
	stream.UseZip64 = UseZip64.Off;
	...

Second solution (and best one speaking about impementation) is to set ZipEntries size before to add them on the zip (stream) archive, leading the library to decide what to do depending on the size of the different entries.

The following code is my current implementation that contains both OnSave with (I hope..) correct handling of Zip64 and GetBitmapFromOraLayer with a little improvement:


	// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>
//
// Copyright (c) 2010 Maia Kozheva <sikon@ubuntu.com>
//
// 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.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Globalization;

namespace PaintNetOraExtension
{
public class OraFileType : FileType
{
	private const int ThumbMaxSize = 256;

	public OraFileType()
	: base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
	{
	}

	/// <summary>
	/// Gets the bitmap from the ora layer.
	/// </summary>
	/// <param name="xofs">The x offset of the layer image.</param>
	/// <param name="yofs">The y offset of the layer image.</param>
	/// <param name="inStream">The input stream containing the layer image.</param>
	/// <param name="baseWidth">The width of the base document.</param>
	/// <param name="baseHeight">The height of the base document.</param>
	private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
	{
	Bitmap image = null;

	using (Bitmap bmp = new Bitmap(inStream))
	{
	PixelFormat pixelFormat = bmp.PixelFormat;

	//check for supported PixelFormat
	if (pixelFormat != PixelFormat.Format24bppRgb && pixelFormat != PixelFormat.Format32bppArgb)
	{
	throw new FormatException("Unknown BitCount");
	}

	int bpp = Bitmap.GetPixelFormatSize(pixelFormat) / 8;

	using (Bitmap layer = new Bitmap(baseWidth, baseHeight, pixelFormat))
	{

	BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, pixelFormat);
	BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, pixelFormat);

	for (int y = 0; y < bmp.Height; y++)
	{
	for (int x = 0; x < bmp.Width; x++)
	{
	byte* dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * bpp);
	byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

	for (int byteCount = 0; byteCount < bpp; byteCount++)
	{
	*dst = *src;
	dst++;
	src++;
	}
	}
	}

	bmp.UnlockBits(bmpData);
	layer.UnlockBits(layerData);
	image = (Bitmap)layer.Clone();
	}
	}

	return image;
	}

	protected override Document OnLoad(Stream input)
	{
	using (ZipFile file = new ZipFile(input))
	{

	XmlDocument stackXml = new XmlDocument();
	stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

	XmlElement imageElement = stackXml.DocumentElement;
	int width = int.Parse(imageElement.GetAttribute("w"));
	int height = int.Parse(imageElement.GetAttribute("h"));

	Document doc = new Document(width, height);

	XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
	XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

	if (layerElements.Count == 0)
	{
	throw new FormatException("No layers found in OpenRaster file");
	}

	int layerCount = layerElements.Count - 1;

	for (int i = layerCount; i >= 0; i--) // The last layer in the list is the background so load in reverse
	{
	XmlElement layerElement = (XmlElement)layerElements[i];

	int x = int.Parse(GetAttribute(layerElement, "x", "0")); // the x offset within the layer
	int y = int.Parse(GetAttribute(layerElement, "y", "0")); // the y offset within the layer

	int layerNum = layerCount - i;

	string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", layerNum));

	ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));

	using (Stream s = file.GetInputStream(zf))
	{
	using (Bitmap bmp = GetBitmapFromOraLayer(x, y, s, width, height))
	{
	BitmapLayer myLayer = null;

	//It's not clear the real difference between CreateBackgroundLayer and 
	//just make a new instance of a BitmapLayer
	if (i == layerCount) // load the background layer first
	{
	myLayer = Layer.CreateBackgroundLayer(width, height);
	}
	else
	{
	myLayer = new BitmapLayer(width, height);
	}

	myLayer.Name = name;
	myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture)));
	myLayer.Visible = (GetAttribute(layerElement, "visibility", "visible") == "visible"); // newer ora files have this

	myLayer.Surface.CopyFromGdipBitmap(bmp, false); // does this make sense?

	myLayer.Metadata.SetUserValue("oraPos", string.Format("{0},{1}", x, y));

	doc.Layers.Insert(layerNum, myLayer);
	}
	}

	}
	return doc;
	}

	}
	// A struct to store the new x,y offsets
	private struct LayerInfo
	{
	public int x;
	public int y;

	public LayerInfo(int x, int y)
	{
	this.x = x;
	this.y = y;
	}
	}

	protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
	{
	using (ZipOutputStream stream = new ZipOutputStream(output))
	{
	//to be the most compatible with other progr.
	// 	stream.UseZip64 = UseZip64.Off;

	byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");

	ZipEntry mimetype = new ZipEntry("mimetype");
	mimetype.Size = databytes.Length;
	mimetype.CompressionMethod = CompressionMethod.Stored;
	stream.PutNextEntry(mimetype);
	stream.Write(databytes, 0, databytes.Length);


	LayerInfo[] layerInfo = new LayerInfo[input.Layers.Count];
	for (int i = 0; i < input.Layers.Count; i++)
	{
	BitmapLayer layer = (BitmapLayer)input.Layers[i];
	Rectangle bounds = layer.Surface.Bounds;

	int left = layer.Width;
	int top = layer.Height;
	int right = 0;
	int bottom = 0;
	unsafe
	{
	for (int y = 0; y < layer.Height; y++)
	{
	ColorBgra* row = layer.Surface.GetRowAddress(y);
	ColorBgra* pixel = row;

	for (int x = 0; x < layer.Width; x++)
	{
	if (pixel->A > 0)
	{

	if (x < left)
	{
	left = x;
	}
	if (x > right)
	{
	right = x;
	}
	if (y < top)
	{
	top = y;
	}
	if (y > bottom)
	{
	bottom = y;
	}

	}
	pixel++;
	}
	}
	}

	if (left < layer.Width && top < layer.Height) // is the layer not empty
	{
	bounds = new Rectangle(left, top, (right - left), (bottom - top)); // clip it to the visible rectangle

	layerInfo[i] = new LayerInfo(left, top);
	}
	else
	{
	layerInfo[i] = new LayerInfo(0, 0);
	}
	byte[] buf = null;
	using (MemoryStream ms = new MemoryStream())
	{
	layer.Surface.CreateAliasedBitmap(bounds, true).Save(ms, ImageFormat.Png);
	buf = ms.ToArray();
	}
	ZipEntry layerEntry = new ZipEntry("data/layer" + i.ToString() + ".png");
	layerEntry.Size = buf.Length;
	stream.PutNextEntry(layerEntry);
	stream.Write(buf, 0, buf.Length);
	}


	ZipEntry stackEntry = new ZipEntry("stack.xml");
	databytes = GetLayerXmlData(input.Layers, layerInfo);
	stackEntry.Size = databytes.Length;
	stream.PutNextEntry(stackEntry);
	stream.Write(databytes, 0, databytes.Length);


	using (Surface flat = new Surface(input.Width, input.Height))
	{
	input.Flatten(flat);
	Size thumbSize = GetThumbDimensions(input.Width, input.Height);

	Surface scale = new Surface(thumbSize);
	scale.FitSurface(ResamplingAlgorithm.SuperSampling, flat);

	using (MemoryStream ms = new MemoryStream())
	{
	scale.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
	databytes = ms.ToArray();
	}
	scale.Dispose();
	}

	ZipEntry thumbEntry = new ZipEntry("Thumbnails/thumbnail.png");
	thumbEntry.Size = databytes.Length;
	stream.PutNextEntry(thumbEntry);
	stream.Write(databytes, 0, databytes.Length);
	}
	}

	private byte[] GetLayerXmlData(LayerList layers, LayerInfo[] info) // OraFormat.cs - minor changes, no idea if still works
	{
	byte[] buf = null;

	using (MemoryStream ms = new MemoryStream())
	{
	XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
	writer.Formatting = Formatting.Indented;

	writer.WriteStartElement("image");
	writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString());
	writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString());

	writer.WriteStartElement("stack");
	writer.WriteAttributeString("opacity", "1");
	writer.WriteAttributeString("name", "root");


	// ORA stores layers top to bottom
	for (int i = layers.Count - 1; i >= 0; i--)
	{

	writer.WriteStartElement("layer");
	writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre 
	writer.WriteAttributeString("name", layers.GetAt(i).Name);
	writer.WriteAttributeString("src", "data/layer" + i.ToString() + ".png");
	writer.WriteAttributeString("visible", layers.GetAt(i).Visible ? "visible" : "hidden");

	writer.WriteAttributeString("x", info[i].x.ToString());
	writer.WriteAttributeString("y", info[i].y.ToString());

	writer.WriteEndElement();
	}

	writer.WriteEndElement(); // stack
	writer.WriteEndElement(); // image

	writer.Close();

	buf = ms.ToArray();
	}

	return buf;
	}

	private Size GetThumbDimensions(int width, int height) // OraFormat.cs
	{
	if (width <= ThumbMaxSize && height <= ThumbMaxSize)
	return new Size(width, height);

	if (width > height)
	return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
	else
	return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
	}

	private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
	{
	string ret = element.GetAttribute(attribute);
	return string.IsNullOrEmpty(ret) ? defValue : ret;
	}
}

public class MyFileTypeFactory : IFileTypeFactory
{
	public FileType[] GetFileTypeInstances()
	{
	return new FileType[] { new OraFileType() };
	}
}
}


The only thing that prevented the GetBitmapFromOraLayer code from working was that the alpha channel was never set to 255 if the image was 24bppRgb.

The background tile should now be loaded and saved correctly.

// Author of OraFormat.cs: 2010 Maia Kozheva <sikon@ubuntu.com>
//
// Copyright (c) 2010 Maia Kozheva <sikon@ubuntu.com>
//
// 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.Drawing;
using System.Xml;
using PaintDotNet;
using PaintDotNet.Data;
using ICSharpCode.SharpZipLib.Zip;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;

namespace OpenRaster_Filetype
{
public class OraFileType : FileType
{
private const int ThumbMaxSize = 256;

public OraFileType()
: base("OpenRaster Image", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SupportsLayers, new String[] { ".ora" })
{
}

/// <summary>
/// Gets the bitmap from the ora layer.
/// </summary>
/// <param name="xofs">The x offset of the layer image.</param>
/// <param name="yofs">The y offset of the layer image.</param>
/// <param name="inStream">The input stream containing the layer image.</param>
/// <param name="baseWidth">The width of the base document.</param>
/// <param name="baseHeight">The height of the base document.</param>
private unsafe Bitmap GetBitmapFromOraLayer(int xofs, int yofs, Stream inStream, int baseWidth, int baseHeight)
{
Bitmap image = null;

using (Bitmap layer = new Bitmap(baseWidth, baseHeight))
{
using (Bitmap bmp = new Bitmap(inStream))
{
BitmapData layerData = layer.LockBits(new Rectangle(xofs, yofs, bmp.Width , bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

int bpp = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; 

for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
byte *dst = (byte*)layerData.Scan0.ToPointer() + (y * layerData.Stride) + (x * 4);
byte* src = (byte*)bmpData.Scan0.ToPointer() + (y * bmpData.Stride) + (x * bpp);

dst[0] = src[0]; // B
dst[1] = src[1]; // G
dst[2] = src[2]; // R

if (bpp == 4) 
{
dst[3] = src[3]; // A
}
else
{
dst[3] = 255;
}
}
}

bmp.UnlockBits(bmpData);
layer.UnlockBits(layerData);
}
image = (Bitmap)layer.Clone();
}

return image;
}

protected override Document OnLoad(Stream input)
{
using (ZipFile file = new ZipFile(input))
{

XmlDocument stackXml = new XmlDocument();
stackXml.Load(file.GetInputStream(file.GetEntry("stack.xml")));

XmlElement imageElement = stackXml.DocumentElement;
int width = int.Parse(imageElement.GetAttribute("w"));
int height = int.Parse(imageElement.GetAttribute("h"));

Document doc = new Document(width, height);

XmlElement stackElement = (XmlElement)stackXml.GetElementsByTagName("stack")[0];
XmlNodeList layerElements = stackElement.GetElementsByTagName("layer");

if (layerElements.Count == 0)
throw new FormatException("No layers found in OpenRaster file");
int layerCount = layerElements.Count - 1;

for (int i = layerCount; i >= 0; i--) // The last layer in the list is the background so load in reverse
{
XmlElement layerElement = (XmlElement)layerElements[i];
int x = int.Parse(GetAttribute(layerElement, "x", "0")); // the x offset within the layer
int y = int.Parse(GetAttribute(layerElement, "y", "0")); // the y offset within the layer

int layerNum = layerCount - i;

string name = GetAttribute(layerElement, "name", string.Format("Layer {0}", layerNum));

ZipEntry zf = file.GetEntry(layerElement.GetAttribute("src"));

using (Stream s = file.GetInputStream(zf))
{
using (Bitmap bmp = GetBitmapFromOraLayer(x, y, s, width, height))
{
BitmapLayer myLayer = null;
if (i == layerCount) // load the background layer first
{
myLayer = Layer.CreateBackgroundLayer(width, height); 
}
else
{
myLayer = new BitmapLayer(width, height); 
}

myLayer.Name = name;
myLayer.Opacity = ((byte)(255.0 * double.Parse(GetAttribute(layerElement, "opacity", "1"), CultureInfo.InvariantCulture))); myLayer.Visible = (GetAttribute(layerElement, "visibility", "visible") == "visible"); // newer ora files have this

myLayer.Surface.CopyFromGdipBitmap(bmp, false); // does this make sense?

string backTile = GetAttribute(layerElement, "background_tile", string.Empty);
if (!string.IsNullOrEmpty(backTile)) 
{
ZipEntry tileZf = file.GetEntry(backTile);
byte[] tileBytes = null;
using (Stream tileStream = file.GetInputStream(tileZf))
{
tileBytes = new byte[(int)tileStream.Length];

int numBytesToRead = (int)tileStream.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
// Read may return anything from 0 to numBytesToRead.
int n = tileStream.Read(tileBytes, numBytesRead, numBytesToRead);
// The end of the file is reached.
if (n == 0)
{
break;
}
numBytesRead += n;
numBytesToRead -= n;
}

}

string tileData = Convert.ToBase64String(tileBytes);
// convert the tile image to a Base64String and then save it in the layer's MetaData.
myLayer.Metadata.SetUserValue("OraBackgroundTile", tileData); 
}


doc.Layers.Insert(layerNum, myLayer);
} 
}

}
return doc;
}

}
// A struct to store the new x,y offsets
private struct LayerInfo
{
public int x;
public int y;

public LayerInfo(int x, int y)
{
this.x = x;
this.y = y;
}
}


protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback)
{
using (ZipOutputStream stream = new ZipOutputStream(output))
{
ZipEntry mimetype = new ZipEntry("mimetype");
mimetype.CompressionMethod = CompressionMethod.Stored;
stream.PutNextEntry(mimetype);

byte[] databytes = System.Text.Encoding.ASCII.GetBytes("image/openraster");
stream.Write(databytes, 0, databytes.Length);

LayerInfo[] layerInfo = new LayerInfo[input.Layers.Count];
for (int i = 0; i < input.Layers.Count; i++)
{
BitmapLayer layer = (BitmapLayer)input.Layers[i];


Rectangle bounds = layer.Surface.Bounds;


int left = layer.Width;
int top = layer.Height;
int right = 0;
int bottom = 0;
unsafe
{
for (int y = 0; y < layer.Height; y++)
{
ColorBgra* row = layer.Surface.GetRowAddress(y);
ColorBgra* pixel = row;

for (int x = 0; x < layer.Width; x++)
{
if (pixel->A > 0)
{

if (x < left)
{
left = x;
}
if (x > right)
{
right = x;
}
if (y < top)
{
top = y;
}
if (y > bottom)
{
bottom = y;
}

}
pixel++;
}
}
}

if (left < layer.Width && top < layer.Height) // is the layer not empty
{

bounds = new Rectangle(left, top, (right - left), (bottom - top)); // clip it to the visible rectangle

layerInfo[i] = new LayerInfo(left, top); 
}
else
{
layerInfo[i] = new LayerInfo(0, 0);
}

string tileData = layer.Metadata.GetUserValue("OraBackgroundTile");
if (!string.IsNullOrEmpty(tileData)) // save the background_tile png if it exists
{
byte[] tileBytes = Convert.FromBase64String(tileData);
stream.PutNextEntry(new ZipEntry("data/background_tile.png"));
stream.Write(tileBytes, 0, tileBytes.Length);
}


byte[] buf = null;
using (MemoryStream ms = new MemoryStream())
{
layer.Surface.CreateAliasedBitmap(bounds, true).Save(ms, ImageFormat.Png);
buf = ms.ToArray();
}
stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString(CultureInfo.InvariantCulture) + ".png"));
stream.Write(buf, 0, buf.Length);
}

/*// OraFormat.cs
for (int i = 0; i < document.Layers.Count; i++)
{
Pixbuf pb = document.Layers[i].Surface.ToPixbuf();
byte[] buf = pb.SaveToBuffer("png");
(pb as IDisposable).Dispose();

stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString() + ".png"));
stream.Write(buf, 0, buf.Length);
}
*/

stream.PutNextEntry(new ZipEntry("stack.xml"));
databytes = GetLayerXmlData(input.Layers, layerInfo);
stream.Write(databytes, 0, databytes.Length);


using (Surface flat = new Surface(input.Width, input.Height))
{
input.Flatten(flat);
Size thumbSize = GetThumbDimensions(input.Width, input.Height);

Surface scale = new Surface(thumbSize);
scale.FitSurface(ResamplingAlgorithm.SuperSampling, flat);

using (MemoryStream ms = new MemoryStream())
{
scale.CreateAliasedBitmap().Save(ms, ImageFormat.Png);
databytes = ms.ToArray();
}
scale.Dispose();
}
stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
stream.Write(databytes, 0, databytes.Length);

/* // OraFormat.cs
ImageSurface flattened = document.GetFlattenedImage();
Pixbuf flattenedPb = flattened.ToPixbuf();
Size newSize = GetThumbDimensions(flattenedPb.Width, flattenedPb.Height);
Pixbuf thumb = flattenedPb.ScaleSimple(newSize.Width, newSize.Height, InterpType.Bilinear);

stream.PutNextEntry(new ZipEntry("Thumbnails/thumbnail.png"));
databytes = thumb.SaveToBuffer("png");
stream.Write(databytes, 0, databytes.Length);

(flattened as IDisposable).Dispose();
(flattenedPb as IDisposable).Dispose();
(thumb as IDisposable).Dispose();
*/
}
}

private byte[] GetLayerXmlData(LayerList layers, LayerInfo[] info) // OraFormat.cs - minor changes, no idea if still works
{
byte[] buf = null;

using(MemoryStream ms = new MemoryStream())
{
XmlTextWriter writer = new XmlTextWriter(ms, System.Text.Encoding.UTF8);
writer.Formatting = Formatting.Indented;

writer.WriteStartElement("image");
writer.WriteAttributeString("w", layers.GetAt(0).Width.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("h", layers.GetAt(0).Height.ToString(CultureInfo.InvariantCulture));

writer.WriteStartElement("stack");
writer.WriteAttributeString("opacity", "1");
writer.WriteAttributeString("name", "root");


// ORA stores layers top to bottom
for (int i = layers.Count - 1; i >= 0; i--)
{
BitmapLayer layer = (BitmapLayer)layers[i];

writer.WriteStartElement("layer");

string backTile = layer.Metadata.GetUserValue("OraBackgroundTile");
if (!string.IsNullOrEmpty(backTile))
{
writer.WriteAttributeString("background_tile", "data/background_tile.png");
}

writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre  writer.WriteAttributeString("name", layers.GetAt(i).Name);
writer.WriteAttributeString("src", "data/layer" + i.ToString(CultureInfo.InvariantCulture) + ".png");
writer.WriteAttributeString("visible", layers.GetAt(i).Visible ? "visible" : "hidden");

writer.WriteAttributeString("x", info[i].x.ToString(CultureInfo.InvariantCulture));
writer.WriteAttributeString("y", info[i].y.ToString(CultureInfo.InvariantCulture));

writer.WriteEndElement();
}

writer.WriteEndElement(); // stack
writer.WriteEndElement(); // image

writer.Close(); 

buf = ms.ToArray();
}

return buf;
}

private Size GetThumbDimensions(int width, int height) // OraFormat.cs
{
if (width <= ThumbMaxSize && height <= ThumbMaxSize)
return new Size(width, height);

if (width > height)
return new Size(ThumbMaxSize, (int)((double)height / width * ThumbMaxSize));
else
return new Size((int)((double)width / height * ThumbMaxSize), ThumbMaxSize);
}

private static string GetAttribute(XmlElement element, string attribute, string defValue) // OraFormat.cs
{
string ret = element.GetAttribute(attribute);
return string.IsNullOrEmpty(ret) ? defValue : ret;
}
}

public class MyFileTypeFactory : IFileTypeFactory
{
public FileType[] GetFileTypeInstances()
{
return new FileType[] { new OraFileType() };
}
}
}

Posted

To Zagna and null54: it would be nice to release the Ora FileType plugin as a compiled plugin that everybody can use (I don't know if there's any license issue related to the code Zagna started with)

It would be also nice to contact http://create.freede...wiki/OpenRaster (the official website of ora format that already links this thread) to add Paint.Net to the ora Ora Application Support List.

Does anybody of you can / want to take care of that?

  • 4 months later...
Posted

So.... I wanted to try and add blend modes but stumbled on a problem.

In GetLayerXmlData, if I use layer.BlendOp.ToString() it returns the localized string instead of a generic english version.

So.... somebody help? What am I doing wrong?

sig2024.jpg.4c3dd6a1ed919373afa78c73a19ed629.jpg

Posted

So.... I wanted to try and add blend modes but stumbled on a problem.

In GetLayerXmlData, if I use layer.BlendOp.ToString() it returns the localized string instead of a generic english version.

So.... somebody help? What am I doing wrong?

layer.BlendOp.GetType().Name

In PDN4, this will be controlled via an enumeration instead. It'll be simpler that way.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...