Zagna Posted December 5, 2010 Posted December 5, 2010 (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 December 5, 2010 by Zagna Quote
null54 Posted December 6, 2010 Posted December 6, 2010 // 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() }; } } } Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint 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
Rick Brewster Posted December 6, 2010 Posted December 6, 2010 ORA eh? Open Raster Format? Do people actually use that? (Not trying to be denigrating, it's an honest question) Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html
Zagna Posted December 7, 2010 Author Posted December 7, 2010 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 // 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 ) fixed saving because it was doing '1,00' on my computer instead of 1.00 So thanks goes to null54 and for you Quote
shrike Posted January 16, 2011 Posted January 16, 2011 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 // 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 ) fixed saving because it was doing '1,00' on my computer instead of 1.00 So thanks goes to null54 and for you Quote
null54 Posted January 16, 2011 Posted January 16, 2011 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() }; } } } Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint 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
Rick Brewster Posted January 17, 2011 Posted January 17, 2011 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. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html
shrike Posted January 19, 2011 Posted January 19, 2011 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. Quote
null54 Posted January 20, 2011 Posted January 20, 2011 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() }; } } } Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint 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
shrike Posted January 20, 2011 Posted January 20, 2011 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() }; } } } Quote
shrike Posted January 24, 2011 Posted January 24, 2011 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? Quote
Zagna Posted May 26, 2011 Author Posted May 26, 2011 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? Quote
Rick Brewster Posted May 26, 2011 Posted May 26, 2011 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. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.