Jump to content
How to Install Plugins ×

OpenRaster Filetype


Zagna

Recommended Posts

Ok, here goes nothing  :Xemote:

So after finding Pinta's OraFormat.cs and pitching it at others here, null54 came to the rescue and fixed everything. And shrike asking to publish it, here's my attempt.

Thanks go to Maia Kozheva and null54. :mtdew:
New 4.1.5 version for 2019 with blend mode change
So there it is, hope it works and.... something.
Edited by Zagna
  • Upvote 1

sig.jpg.7f312affa740bae49243c4439bc4a244.jpg

Link to comment
Share on other sites

I can easily open .ora files created with MyPaint and that error sounds like it's file in a strange place rather than the plugin? No idea.

Can you put a PNG or JPG file the same folder and do they open?

sig.jpg.7f312affa740bae49243c4439bc4a244.jpg

Link to comment
Share on other sites

This is what is inside .Ora file:

ev2lw6.png

And this is the message I get when try to open it whit Paint.NET :

qyizx3.png

"No se pudo encontrar el Archivo" = "Could not find file"

Edited by arcr
Link to comment
Share on other sites

It seems like it is not able to find a file within the ora file, but the ora looks like it is correct in the screen shots. :/

Could you attach the ora file in a zip?

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

 

Link to comment
Share on other sites

Both. It's a filetype plugin :D

Link to comment
Share on other sites

So it goes into the File Type folder or Effect folder? Sorry for these questions, the OP didn't make it clear where it should go.

Officially retired from this forum. Have a nice day.

Link to comment
Share on other sites

The thread title is "OpenRaster Filetype". Is that not clear enough? :/

Link to comment
Share on other sites

Hi,

first: Thank you very very much!!!! I'm working a lot with MyPaint and Paint.Net and you Plugin is exactly what i need.

I can open an .ora file without problems into Paint.Net - this is really perfect.

but....

I can't save any images from Paint.Net to .ora ... everytime i try i just get the screen: "an unknown error appears while saving" (see attached german screen)

I don't know what to do... pleeeease help :)

Some info: Paint.NET v3.5.7 , MyPaint 0.9.0, Windows Vista with all updates.

post-81471-129854729336_thumb.png

Edited by CH5771
Link to comment
Share on other sites

I have no idea what the problem is. It used to work fine earlier..... what if it's due to 3.5.7's changes.... no idea at all.

It's happening to me too, PDN 3.5.7 and Win 7 SP1.

sig.jpg.7f312affa740bae49243c4439bc4a244.jpg

Link to comment
Share on other sites

I hate it... "This update improves reliability of saving" ... or non-saving ... *hmpf*... so back to the developers discussion to get it fixed :)

Edited by CH5771
Link to comment
Share on other sites

The problem was that the ZipOutputStream closes the output stream when it is done instead of letting Paint.NET handle that.

MyPaint's stroke map data should now be preserved when loading and saving as well.

// 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.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" })
{
strokeMapVersions = new string[2] { "mypaint_strokemap", "mypaint_strokemap_v2" };
}

/// <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;
}
/// <summary>
/// The formats for MyPaint's stroke map, version 1 and version 2.
/// </summary>
private readonly string[] strokeMapVersions;
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"), CultureInfo.InvariantCulture);
int height = int.Parse(imageElement.GetAttribute("h"), CultureInfo.InvariantCulture);

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"), CultureInfo.InvariantCulture); // the x offset within the layer
int y = int.Parse(GetAttribute(layerElement, "y", "0"), CultureInfo.InvariantCulture); // 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);
}

foreach (string version in strokeMapVersions)
{
string strokeMap = GetAttribute(layerElement, version, string.Empty);
if (!string.IsNullOrEmpty(strokeMap))
{
ZipEntry strokeZf = file.GetEntry(strokeMap);
byte[] strokeBytes = null;
using (Stream strokeStream = file.GetInputStream(strokeZf))
{
strokeBytes = new byte[(int)strokeStream.Length];

int numBytesToRead = (int)strokeStream.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
// Read may return anything from 0 to numBytesToRead.
int n = strokeStream.Read(strokeBytes, numBytesRead, numBytesToRead);
// The end of the file is reached.
if (n == 0)
{
break;
}
numBytesRead += n;
numBytesToRead -= n;
}
}
string strokeData = Convert.ToBase64String(strokeBytes);
// convert the stroke map to a Base64String and then save it in the layer's MetaData.

myLayer.Metadata.SetUserValue("OraMyPaintStrokeMapData", strokeData);

// Save the version of the stroke map in the MetaData
myLayer.Metadata.SetUserValue("OraMyPaintStrokeMapVersion", version);
}
}

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))
{
stream.IsStreamOwner = false;
stream.UseZip64 = UseZip64.Off;
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);
}

string strokeData = layer.Metadata.GetUserValue("OraMyPaintStrokeMapData");
if (!string.IsNullOrEmpty(strokeData)) // save MyPaint's stroke data if it exists
{
byte[] tileBytes = Convert.FromBase64String(strokeData);
stream.PutNextEntry(new ZipEntry("data/layer" + i.ToString(CultureInfo.InvariantCulture) + "_strokemap.dat"));
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);
}
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);
}
System.Diagnostics.Debug.WriteLine("All done here");
}

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");
}
string strokeMapVersion = layer.Metadata.GetUserValue("OraMyPaintStrokeMapVersion");
if (!string.IsNullOrEmpty(strokeMapVersion)) 
{
writer.WriteAttributeString(strokeMapVersion, "data/layer" + i.ToString(CultureInfo.InvariantCulture) + "_strokemap.dat");
}


writer.WriteAttributeString("opacity", ((double)(layers.GetAt(i).Opacity / 255.0)).Clamp(0.0, 1.0).ToString("N2", CultureInfo.InvariantCulture)); // this is even more bizarre 
if (string.IsNullOrEmpty(strokeMapVersion)) // the stroke map layer does not have a name
{
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

 

Link to comment
Share on other sites

The .exe doesn't magically update itself :)

When null54 saves the plugin by giving updated code, I gotta copy that code into VS 2010, compile it, test it quickly, wrap it in the NSIS installer and then copy it to the download location.

And right now the installer has been updated with null54's latest version so it should work once again :D

sig.jpg.7f312affa740bae49243c4439bc4a244.jpg

Link to comment
Share on other sites

Hmmm no magic autoupdate?...

But for me it feels thats you both are grand master wizzards :)

Thanks for you both - you save my day! It works again!!!! PERFECT!!!

Edited by CH5771
Link to comment
Share on other sites

I just noticed there's a lil bug somewhere in the code...

stack.xml has the right dimensions, but for some reason the lowest and rightmost strips of pixels are discarded completely.

so layer0.png is 1 pixel shorter and thinner....

null54 to the rescue?

Edit: the culprit seems the be bounds in OnSave and the unsafe bit...

Edit2: +1 addition to the rescue! :P .exe in the first post has been updated

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

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

Edited by Zagna

sig.jpg.7f312affa740bae49243c4439bc4a244.jpg

Link to comment
Share on other sites

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...