Jump to content

shrike

Newbies
  • Posts

    4
  • Joined

  • Last visited

shrike's Achievements

Newbie

Newbie (1/14)

  • First Post
  • Week One Done
  • One Month Later
  • One Year In

Recent Badges

0

Reputation

  1. 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?
  2. 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() }; } } }
  3. 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.
  4. 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));
×
×
  • Create New...