forked from PintaProject/Pinta
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added capability to export to portable pixmap (PintaProject#549)
* Added capability to export to portable pixmap * Added file equality utility method and one test * Added more tests and more files (two versions of the output ppm) * Copying assets to output dir * Added extra line break to sample output files * Exporter refactored in order to be able to test. Also, finished tests attempt * Refactored file path retrieval out of `DataInputStreamContext` constructor * Initializing modules. Maybe that's the reason why it can't find GLib? * Corrected file names * Corrected remaining file name * Deleted final line in test outputs * Corrected bug in test * Added commented-out console messages * Added extra line * Commented out test case that acts weird --------- Co-authored-by: Lehonti Ramos <lehonti@ramos>
- Loading branch information
Showing
8 changed files
with
233 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,42 @@ | ||
using System; | ||
using System.IO; | ||
using System.Text; | ||
using Cairo; | ||
using Gtk; | ||
|
||
namespace Pinta.Core; | ||
|
||
public sealed class NetpbmPortablePixmap : IImageExporter | ||
{ | ||
public void Export (ImageSurface flattenedImage, Stream outputStream) | ||
{ | ||
using StreamWriter writer = new (outputStream, Encoding.ASCII); | ||
Size imageSize = flattenedImage.GetSize (); | ||
ReadOnlySpan<ColorBgra> pixelData = flattenedImage.GetReadOnlyPixelData (); | ||
writer.WriteLine ("P3"); // Magic number for text-based portable pixmap format | ||
writer.WriteLine ($"{imageSize.Width} {imageSize.Height}"); | ||
writer.WriteLine ("255"); | ||
for (int row = 0; row < imageSize.Height; row ) { | ||
int rowStart = row * imageSize.Width; | ||
int rowEnd = rowStart imageSize.Width; | ||
for (int index = rowStart; index < rowEnd; index ) { | ||
ColorBgra color = pixelData[index]; | ||
string r = color.R.ToString ().PadLeft (3, ' '); | ||
string g = color.G.ToString ().PadLeft (3, ' '); | ||
string b = color.B.ToString ().PadLeft (3, ' '); | ||
writer.Write ($"{r} {g} {b}"); | ||
if (index != rowEnd - 1) | ||
writer.Write (" "); | ||
} | ||
writer.WriteLine (); | ||
} | ||
writer.Close (); | ||
} | ||
|
||
public void Export (Document document, Gio.File file, Window parent) | ||
{ | ||
ImageSurface flattenedImage = document.GetFlattenedImage (); | ||
using GioStream outputStream = new (file.Replace ()); | ||
Export (flattenedImage, outputStream); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,5 @@ | ||
P3 | ||
3 2 | ||
255 | ||
255 0 0 0 255 0 0 0 255 | ||
255 255 0 255 255 255 0 0 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,5 @@ | ||
P3 | ||
3 2 | ||
255 | ||
255 0 0 0 255 0 0 0 255 | ||
255 255 0 255 255 255 0 0 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,52 @@ | ||
using System.Collections.Generic; | ||
using Cairo; | ||
using NUnit.Framework; | ||
|
||
namespace Pinta.Core.Tests; | ||
|
||
[TestFixture] | ||
internal sealed class FileFormatTests | ||
{ | ||
[TestCase ("sixcolorsinput.gif", "sixcolorsoutput_lf.ppm")] | ||
[TestCase ("sixcolorsinput.gif", "sixcolorsoutput_crlf.ppm")] | ||
//[TestCase ("sixcolorsoutput_lf.ppm", "sixcolorsoutput_crlf.ppm")] // TODO: Test reads them as equal, but they're not | ||
public void Files_NotEqual (string file1, string file2) | ||
{ | ||
var path1 = Utilities.GetAssetPath (file1); | ||
var path2 = Utilities.GetAssetPath (file2); | ||
Assert.IsFalse (Utilities.AreFilesEqual (path1, path2)); | ||
} | ||
|
||
[TestCaseSource (nameof (netpbm_pixmap_text_cases))] | ||
public void Export_NetpbmPixmap_TextBased (string inputFile, IEnumerable<string> acceptableOutputs) | ||
{ | ||
var inputFilePath = Utilities.GetAssetPath (inputFile); | ||
ImageSurface loaded = Utilities.LoadImage (inputFilePath); | ||
NetpbmPortablePixmap exporter = new (); | ||
Gio.MemoryOutputStream memoryOutput = Gio.MemoryOutputStream.NewResizable (); | ||
using GioStream outputStream = new (memoryOutput); | ||
exporter.Export (loaded, outputStream); | ||
outputStream.Close (); | ||
memoryOutput.Close (null); | ||
var exportedBytes = memoryOutput.StealAsBytes (); | ||
bool matched = false; | ||
foreach (string fileName in acceptableOutputs) { | ||
var bytesStream = Gio.MemoryInputStream.NewFromBytes (exportedBytes); | ||
var bytesReader = Gio.DataInputStream.New (bytesStream); | ||
var filePath = Utilities.GetAssetPath (fileName); | ||
using var context = Utilities.OpenFile (filePath); | ||
if (Utilities.AreFilesEqual (bytesReader, context.DataStream)) { | ||
matched = true; | ||
break; | ||
} | ||
} | ||
Assert.IsTrue (matched); | ||
} | ||
|
||
static readonly IReadOnlyList<TestCaseData> netpbm_pixmap_text_cases = new TestCaseData[] { | ||
new ( | ||
"sixcolorsinput.gif", | ||
new [] { "sixcolorsoutput_lf.ppm", "sixcolorsoutput_crlf.ppm" } | ||
), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,110 @@ | ||
using System; | ||
using Cairo; | ||
|
||
namespace Pinta.Core.Tests; | ||
|
||
internal static class Utilities | ||
{ | ||
static Utilities () | ||
{ | ||
Gio.Module.Initialize (); | ||
GdkPixbuf.Module.Initialize (); | ||
Cairo.Module.Initialize (); | ||
Gdk.Module.Initialize (); | ||
} | ||
|
||
/// <returns> | ||
/// <see langword="true"/> if the files with these file names | ||
/// are byte-for-byte the same, <see langword="false"/> if not | ||
/// </returns> | ||
internal static bool AreFilesEqual (string fileName1, string fileName2) | ||
{ | ||
var context1 = OpenFile (fileName1); | ||
var context2 = OpenFile (fileName2); | ||
return AreFilesEqual (context1.DataStream, context2.DataStream); ; | ||
} | ||
|
||
internal static DataInputStreamContext OpenFile (string filePath) | ||
{ | ||
return new (filePath); | ||
} | ||
|
||
internal static string GetAssetPath (string fileName) | ||
{ | ||
const string ASSETS_FOLDER = "Assets"; | ||
string assemblyPath = System.IO.Path.GetDirectoryName (typeof (Utilities).Assembly.Location)!; | ||
return System.IO.Path.Combine (assemblyPath, ASSETS_FOLDER, fileName); | ||
} | ||
|
||
internal sealed class DataInputStreamContext : IDisposable | ||
{ | ||
private Gio.FileInputStream FileStream { get; } | ||
public Gio.DataInputStream DataStream { get; } | ||
|
||
internal DataInputStreamContext (string filePath) | ||
{ | ||
Gio.File file = Gio.FileHelper.NewForPath (filePath); | ||
Gio.FileInputStream fs = file.Read (null); | ||
FileStream = fs; | ||
DataStream = Gio.DataInputStream.New (fs); | ||
} | ||
|
||
public void Dispose () | ||
{ | ||
DataStream.Dispose (); | ||
FileStream.Dispose (); | ||
} | ||
} | ||
|
||
internal static bool AreFilesEqual (Gio.DataInputStream dataStream1, Gio.DataInputStream dataStream2) | ||
{ | ||
dataStream1.Seek (0, GLib.SeekType.Set, null); | ||
dataStream2.Seek (0, GLib.SeekType.Set, null); | ||
const int BUFFER_SIZE = 4096; | ||
Span<byte> buffer1 = stackalloc byte[BUFFER_SIZE]; | ||
Span<byte> buffer2 = stackalloc byte[BUFFER_SIZE]; | ||
while (true) { | ||
|
||
long bytesRead1 = dataStream1.Read (buffer1, null); | ||
long bytesRead2 = dataStream2.Read (buffer2, null); | ||
|
||
//Console.WriteLine ($"1: {bytesRead1} bytes read"); | ||
//Console.WriteLine ($"2: {bytesRead2} bytes read"); | ||
|
||
if (bytesRead1 != bytesRead2) // Different file sizes | ||
{ | ||
//Console.WriteLine ("Different file sizes"); | ||
return false; | ||
} | ||
|
||
if (bytesRead1 == 0) // End of file | ||
{ | ||
//Console.WriteLine ("End of file"); | ||
return true; | ||
} | ||
|
||
for (int i = 0; i < bytesRead1; i ) { | ||
if (buffer1[i] != buffer2[i]) // Differing byte | ||
{ | ||
//Console.WriteLine ($"Differing byte at position {i} of buffer"); | ||
return false; | ||
} | ||
} | ||
} | ||
} | ||
|
||
public static ImageSurface LoadImage (string imageFilePath) | ||
{ | ||
var file = Gio.FileHelper.NewForPath (imageFilePath); | ||
using var fs = file.Read (null); | ||
try { | ||
var bg = GdkPixbuf.Pixbuf.NewFromStream (fs, cancellable: null); | ||
var surf = CairoExtensions.CreateImageSurface (Format.Argb32, bg.Width, bg.Height); | ||
var context = new Cairo.Context (surf); | ||
context.DrawPixbuf (bg, 0, 0); | ||
return surf; | ||
} finally { | ||
fs.Close (null); | ||
} | ||
} | ||
} |