preliminary improvements to multimedia-picture plugin

This commit is contained in:
Michael Becker 2021-07-11 20:29:48 -04:00
parent 6802fe19ea
commit 3183a87127
No known key found for this signature in database
GPG Key ID: 98C333A81F18C22C
17 changed files with 1159 additions and 52 deletions

View File

@ -19,8 +19,12 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using MBS.Framework;
using MBS.Framework.UserInterface;
using MBS.Framework.UserInterface.Layouts;
using UniversalEditor.ObjectModels.Multimedia.Picture;
namespace UniversalEditor.Editors.Multimedia.Picture
{
partial class PictureEditor
@ -38,5 +42,41 @@ namespace UniversalEditor.Editors.Multimedia.Picture
this.Layout = new BoxLayout(Orientation.Vertical);
this.Controls.Add(da, new BoxLayout.Constraints(true, true));
}
protected override void OnCreated(EventArgs e)
{
base.OnCreated(e);
Context.AttachCommandEventHandler("ImageTransformRotateClockwise", ImageTransformRotateClockwise_Click);
Context.AttachCommandEventHandler("ImageTransformRotateCounterclockwise", ImageTransformRotateCounterclockwise_Click);
Context.AttachCommandEventHandler("ImageTransformRotate180", ImageTransformRotate180_Click);
Context.AttachCommandEventHandler("ImageTransformRotateArbitrary", ImageTransformRotateArbitrary_Click);
}
private void ImageTransformRotateClockwise_Click(object sender, EventArgs e)
{
BeginEdit();
(this.ObjectModel as PictureObjectModel).Rotate(90);
this.da.Picture = (this.ObjectModel as PictureObjectModel);
EndEdit();
}
private void ImageTransformRotateCounterclockwise_Click(object sender, EventArgs e)
{
BeginEdit();
(this.ObjectModel as PictureObjectModel).Rotate(-90);
this.da.Picture = (this.ObjectModel as PictureObjectModel);
EndEdit();
}
private void ImageTransformRotate180_Click(object sender, EventArgs e)
{
BeginEdit();
(this.ObjectModel as PictureObjectModel).Rotate(180);
this.da.Picture = (this.ObjectModel as PictureObjectModel);
EndEdit();
}
private void ImageTransformRotateArbitrary_Click(object sender, EventArgs e)
{
}
}
}

View File

@ -37,13 +37,13 @@ namespace UniversalEditor.Plugins.Multimedia.UserInterface
/// <param name="pic">The <see cref="PictureObjectModel" /> containing the image data to convert.</param>
public static Image ToImage(this PictureObjectModel pic)
{
byte[] input = pic.ToByteArray();
byte[] input = pic.ToByteArray(PixelFormat.RGBA);
byte[] output = new byte[input.Length];
for (int i = 0; i < input.Length; i += 4)
{
byte b = input[i];
byte r = input[i];
byte g = input[i + 1];
byte r = input[i + 2];
byte b = input[i + 2];
byte a = input[i + 3];
output[i] = r;

View File

@ -3,7 +3,7 @@
<Associations>
<Association>
<Filters>
<Filter Title="Chaos Works Engine palette">
<Filter Title="Chaos Works Engine palette" ContentType="application/x-chaosworks-spp" HintComparison="FilterThenMagic">
<FileNameFilters>
<FileNameFilter>*.spp</FileNameFilter>
</FileNameFilters>

View File

@ -154,6 +154,8 @@ namespace UniversalEditor.Plugins.ChaosWorks.DataFormats.Multimedia.PictureColle
}
br.Accessor.LoadPosition();
palette = EmbeddedPalette;
// now that we've loaded the frame definitions and embedded color palette,
// we can go back and read the pixel data
List<List<byte>> lists = new List<List<byte>>();

View File

@ -0,0 +1,32 @@
//
// EXRChannel.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public class EXRChannel
{
public string Name { get; set; } = null;
public EXRPixelType PixelType { get; set; } = EXRPixelType.UInt;
public bool IsLinear { get; set; } = false;
public int XSampling { get; set; } = 0;
public int YSampling { get; set; } = 0;
}
}

View File

@ -0,0 +1,35 @@
//
// EXRChromaticities.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public class EXRChromaticities
{
public float RedX { get; set; }
public float RedY { get; set; }
public float GreenX { get; set; }
public float GreenY { get; set; }
public float BlueX { get; set; }
public float BlueY { get; set; }
public float WhiteX { get; set; }
public float WhiteY { get; set; }
}
}

View File

@ -0,0 +1,37 @@
//
// EXRCompression.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public enum EXRCompression
{
None,
RunLengthEncoding,
ZipPerScanline,
ZipPer16Scanline,
PIZWavelet,
PXR24Deflate,
B44,
B44A,
DWAA,
DWAB
}
}

View File

@ -0,0 +1,431 @@
//
// EXRBaseDataFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using MBS.Framework.Drawing;
using MBS.Framework.Settings;
using UniversalEditor.Accessors;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.Multimedia.Picture;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public class EXRDataFormat : DataFormat
{
public EXRDataFormat()
{
}
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = base.MakeReferenceInternal();
_dfr.Capabilities.Add(typeof(PictureObjectModel), DataFormatCapabilities.All);
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting("LineOrder", "_Line order", EXRLineOrder.IncreasingY, new ChoiceSetting.ChoiceSettingValue[]
{
new ChoiceSetting.ChoiceSettingValue("IncreasingY", "Increasing", EXRLineOrder.IncreasingY),
new ChoiceSetting.ChoiceSettingValue("DecreasingY", "Decreasing", EXRLineOrder.DecreasingY),
new ChoiceSetting.ChoiceSettingValue("RandomY", "Random", EXRLineOrder.RandomY)
}));
}
return _dfr;
}
public readonly byte[] EXR_SIGNATURE = new byte[] { 0x76, 0x2F, 0x31, 0x01 };
public Dictionary<string, object> Properties { get; } = new Dictionary<string, object>();
public EXRLineOrder LineOrder { get; set; } = EXRLineOrder.IncreasingY;
protected override void LoadInternal(ref ObjectModel objectModel)
{
PictureObjectModel pic = (objectModel as PictureObjectModel);
if (pic == null)
throw new ObjectModelNotSupportedException();
byte[] signature = Accessor.Reader.ReadBytes(4);
if (!signature.Match(EXR_SIGNATURE))
throw new InvalidDataFormatException("file does not begin with exr signature");
byte version = Accessor.Reader.ReadByte();
EXRFlags flags = (EXRFlags) Accessor.Reader.ReadUInt24();
if ((flags & EXRFlags.Multipart) == EXRFlags.Multipart)
{
}
// The header component of the single-part file holds a single header (for single-part files).
// Each header is a sequence of attributes ended by a null byte.
// The file has the same structure as a 1.7 file. That is, the multi-part bit(bit 12) must be 0, and the single null
// byte that signals the end of the headers must be omitted. This structure also applies to single-part deep data
// files.
ReadProperties(Accessor);
RequireProperty(new string[] { "dataWindow", "displayWindow", "lineOrder", "compression", "channels", "chromaticities" });
LineOrder = (EXRLineOrder)Properties["lineOrder"];
Vector2D screenWindowCenter = new Vector2D(0, 0);
PreferProperty("screenWindowCenter", ref screenWindowCenter);
int width = (int)((Rectangle)Properties["dataWindow"]).Width + 1;
int height = (int)((Rectangle)Properties["dataWindow"]).Height + 1;
pic.Width = width;
pic.Height = height;
int chunkCount = 0;
if (Properties.ContainsKey("chunkCount"))
{
chunkCount = (int)Properties["chunkCount"];
}
else if (((flags & EXRFlags.Multipart) == EXRFlags.Multipart) || (flags & EXRFlags.NonImage) == EXRFlags.NonImage)
{
throw new InvalidDataFormatException("multipart or non-image EXR file does not contain a chunkCount attribute");
}
else
{
chunkCount = height / 16;
}
ulong[] chunkOffsets = new ulong[chunkCount];
for (int i = 0; i < chunkCount; i++)
{
chunkOffsets[i] = Accessor.Reader.ReadUInt64();
}
int scanlinesPerBlock = 0;
EXRCompression compressionMethod = (EXRCompression)Properties["compression"];
switch (compressionMethod)
{
case EXRCompression.None:
case EXRCompression.RunLengthEncoding:
case EXRCompression.ZipPerScanline:
{
scanlinesPerBlock = 1;
break;
}
case EXRCompression.ZipPer16Scanline:
case EXRCompression.PXR24Deflate:
{
scanlinesPerBlock = 16;
break;
}
case EXRCompression.PIZWavelet:
case EXRCompression.B44:
case EXRCompression.B44A:
{
scanlinesPerBlock = 32;
break;
}
}
EXRChannel[] channels = (EXRChannel[])Properties["channels"];
EXRChromaticities chromaticities = (EXRChromaticities)Properties["chromaticities"];
for (int i = 0; i < chunkCount; i++)
{
int scanlineOffsetY = Accessor.Reader.ReadInt32();
uint compressedLength = Accessor.Reader.ReadUInt32();
byte[] compressedData = Accessor.Reader.ReadBytes(compressedLength);
byte[] decompressedData = (new Compression.Modules.Zlib.ZlibCompressionModule()).Decompress(compressedData);
MemoryAccessor ma = new MemoryAccessor(decompressedData);
switch (LineOrder)
{
case EXRLineOrder.IncreasingY:
{
for (int y = 0; y < scanlinesPerBlock; y++)
{
ReadChannels(ma.Reader, pic, channels, scanlineOffsetY + y);
}
break;
}
case EXRLineOrder.DecreasingY:
{
for (int y = scanlinesPerBlock - 1; y >= 0; y--)
{
ReadChannels(ma.Reader, pic, channels, scanlineOffsetY + y);
}
break;
}
}
System.IO.File.WriteAllBytes(String.Format("/tmp/zlib{0}.dat", scanlineOffsetY), decompressedData);
}
}
/// <summary>
/// Converts the attribute data in <paramref name="data" /> to a <see cref="Object" /> instance of the appropriate
/// type as indicated by <paramref name="dataType" />.
///
/// This method can be overridden in derived classes to implement support for data types other than those defined in
/// the official OpenEXR specifications. To ensure backward compatibility with official OpenEXR implementations, it
/// is recommended that you return the value returned from base.ConvertAttributeData(...) if it is not null (i.e., if
/// support for that data type is already implemented in the base class).
/// </summary>
/// <returns>The attribute data.</returns>
/// <param name="data">Data.</param>
/// <param name="dataType">Data type.</param>
protected virtual object ConvertAttributeData(byte[] data, string dataType)
{
object propertyData = null;
MemoryAccessor acc = new MemoryAccessor(data);
switch (dataType)
{
case "chlist":
{
List<EXRChannel> chlist = new List<EXRChannel>();
while (!acc.Reader.EndOfStream)
{
string name = acc.Reader.ReadNullTerminatedString();
if (String.IsNullOrEmpty(name))
break;
EXRChannel channel = new EXRChannel();
channel.Name = name;
channel.PixelType = (EXRPixelType)acc.Reader.ReadInt32();
channel.IsLinear = (acc.Reader.ReadUInt32() == 1);
channel.XSampling = acc.Reader.ReadInt32();
channel.YSampling = acc.Reader.ReadInt32();
chlist.Add(channel);
}
propertyData = chlist.ToArray();
break;
}
case "chromaticities":
{
EXRChromaticities chromaticities = new EXRChromaticities();
chromaticities.RedX = acc.Reader.ReadSingle();
chromaticities.RedY = acc.Reader.ReadSingle();
chromaticities.GreenX = acc.Reader.ReadSingle();
chromaticities.GreenY = acc.Reader.ReadSingle();
chromaticities.BlueX = acc.Reader.ReadSingle();
chromaticities.BlueY = acc.Reader.ReadSingle();
chromaticities.WhiteX = acc.Reader.ReadSingle();
chromaticities.WhiteY = acc.Reader.ReadSingle();
propertyData = chromaticities;
break;
}
case "compression":
{
if (data.Length == 1)
{
propertyData = (EXRCompression)acc.Reader.ReadByte();
}
else
{
throw new InvalidDataFormatException("property data size mismatch");
}
break;
}
case "lineOrder":
{
if (data.Length == 1)
{
propertyData = (EXRLineOrder)acc.Reader.ReadByte();
}
else
{
throw new InvalidDataFormatException("property data size mismatch");
}
break;
}
case "box2i":
{
if (data.Length == 16)
{
uint x = acc.Reader.ReadUInt32();
uint y = acc.Reader.ReadUInt32();
uint w = acc.Reader.ReadUInt32();
uint h = acc.Reader.ReadUInt32();
propertyData = new MBS.Framework.Drawing.Rectangle(x, y, w, h);
}
else
{
throw new InvalidDataFormatException("property data size mismatch");
}
break;
}
case "v2f":
{
if (data.Length == 8)
{
float x = acc.Reader.ReadSingle();
float y = acc.Reader.ReadSingle();
propertyData = new MBS.Framework.Drawing.Vector2D(x, y);
}
else
{
throw new InvalidDataFormatException("property data size mismatch");
}
break;
}
case "float":
{
if (data.Length == 4)
{
propertyData = acc.Reader.ReadSingle();
}
else
{
throw new InvalidDataFormatException("property data size mismatch");
}
break;
}
}
return propertyData;
}
private void ReadChannels(Reader reader, PictureObjectModel pic, EXRChannel[] channels, int y)
{
for (int c = 0; c < channels.Length; c++)
{
for (int x = 0; x < pic.Width; x++)
{
Color color = pic.GetPixel(x, y);
color.A = 1.0;
switch (channels[c].Name)
{
case "R":
{
color.R = ReadPixelComponent(reader, channels[c].PixelType);
break;
}
case "G":
{
color.G = ReadPixelComponent(reader, channels[c].PixelType);
break;
}
case "B":
{
color.B = ReadPixelComponent(reader, channels[c].PixelType);
break;
}
default:
{
Console.Error.WriteLine("channel '{0}' not handled", channels[c].Name);
break;
}
}
Console.WriteLine("setpixel ({0}, {1}) : {2}", x, y, color.ToString());
pic.SetPixel(color, x, y);
}
}
}
/// <summary>
/// Indicates that a property with the given
/// <paramref name="propertyName" /> is preferred to be defined; however,
/// if the property is not defined, use the value specified by
/// <paramref name="propertyValue" />.
/// </summary>
/// <param name="propertyName">The name of the property to check.</param>
/// <param name="propertyValue">The value to assign property to.</param>
/// <typeparam name="T">The expected type of the property.</typeparam>
private void PreferProperty<T>(string propertyName, ref T propertyValue)
{
if (Properties.ContainsKey(propertyName))
{
object value = Properties[propertyName];
if (value is T)
{
propertyValue = (T)Properties[propertyName];
}
else
{
Console.Error.WriteLine(String.Format("exr: required attribute '{0}' not of type '{1}'; assuming {2}", propertyName, typeof(T).Name, propertyValue));
}
}
else
{
Console.Error.WriteLine(String.Format("exr: required attribute '{0}' not found; assuming {1}", propertyName, propertyValue));
}
}
private void RequireProperty(string propertyName)
{
RequireProperty(new string[] { propertyName });
}
private void RequireProperty(string[] propertyNames)
{
for (int i = 0; i < propertyNames.Length; i++)
{
if (!Properties.ContainsKey(propertyNames[i]))
{
throw new InvalidDataFormatException(String.Format("exr file does not contain required '{0}' attribute", propertyNames[i]));
}
}
}
private double ReadPixelComponent(Reader reader, EXRPixelType pixelType)
{
switch (pixelType)
{
case EXRPixelType.Float:
{
return (double)reader.ReadSingle();
}
case EXRPixelType.Half:
{
ushort value = reader.ReadUInt16();
return (double)value;
}
case EXRPixelType.UInt:
{
return (double)reader.ReadUInt32();
}
}
throw new NotImplementedException();
}
private void ReadProperties(Accessor acc)
{
while (!acc.Reader.EndOfStream)
{
string propertyName = acc.Reader.ReadNullTerminatedString();
if (String.IsNullOrEmpty(propertyName))
break;
string propertyDataType = acc.Reader.ReadNullTerminatedString();
uint propertyDataSize = acc.Reader.ReadUInt32();
object propertyData = null;
byte[] propertyDataBytes = acc.Reader.ReadBytes(propertyDataSize);
propertyData = ConvertAttributeData(propertyDataBytes, propertyDataType);
if (propertyData == null)
{
propertyData = propertyDataBytes;
}
Properties[propertyName] = propertyData;
}
}
protected override void SaveInternal(ObjectModel objectModel)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,58 @@
//
// EXRFlags.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
[Flags()]
public enum EXRFlags
{
/// <summary>
/// If set, this is a regular single-part image and the pixels are stored
/// as tiles, and <see cref="NonImage" /> and <see cref="Multipart"/>
/// flags must NOT be set.
///
/// This bit is for backwards compatibility with older libraries: it is
/// only set when there is one "normal" tiled image in the file.
/// </summary>
SingleTile = 0x200,
/// <summary>
/// If set, the maximum length of attribute names, attribute type names,
/// and channel names is 255 bytes. If not set, the maximum length is
/// 31 bytes.
/// </summary>
LongName = 0x400,
/// <summary>
/// If set, there is at least one part which is not a regular scan line
/// image or regular tiled image (that is, it is a deep format). If not
/// set, all parts are entirely single or multiple scan line or tiled
/// images.
/// </summary>
NonImage = 0x800,
/// <summary>
/// If set, the file does not contain exactly 1 part and the 'end of
/// header' byte must be included at the end of each header part, and
/// the part number fields must be added to the chunks. If not set, this
/// is not a multi-part file and the 'end of header' byte and part number
/// fields in chunks must be omitted. New in 2.0.
/// </summary>
Multipart = 0x1000
}
}

View File

@ -0,0 +1,43 @@
//
// EXRLineOrder.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public enum EXRLineOrder
{
/// <summary>
/// Indicates that scan lines are stored in order of increasing Y
/// coordinates.
/// </summary>
IncreasingY = 0,
/// <summary>
/// Indicates that scan lines are stored in order of decreasing Y
/// coordinates.
/// </summary>
DecreasingY = 1,
/// <summary>
/// Indicates that scan lines are stored randomly. The proper order of
/// scan lines can be found by reading the offset table, in which scan
/// line offsets are stored in order of increasing Y coordinates.
/// </summary>
RandomY = 2
}
}

View File

@ -0,0 +1,30 @@
//
// EXRPixelType.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.EXR
{
public enum EXRPixelType
{
UInt = 0,
Half = 1,
Float = 2
}
}

View File

@ -38,6 +38,8 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
public const int BITMAP_PALETTE_ENTRY_SIZE_24BIT = 3;
public const int BITMAP_PALETTE_ENTRY_SIZE_32BIT = 4;
public BitmapCompression Compression { get; set; } = BitmapCompression.None;
/// <summary>
/// The number of bits-per-pixel. The biBitCount member of the BITMAPINFOHEADER structure determines the
/// number of bits that define each pixel and the maximum number of colors in the bitmap.
@ -57,6 +59,15 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
new ChoiceSetting.ChoiceSettingValue("Monochrome", "Monochrome", BitmapBitsPerPixel.Monochrome),
new ChoiceSetting.ChoiceSettingValue("TrueColor", "True color (24-bit R8G8B8)", BitmapBitsPerPixel.TrueColor)
}));
dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting(nameof(Compression), "_Compression", BitmapCompression.None, new ChoiceSetting.ChoiceSettingValue[]
{
new ChoiceSetting.ChoiceSettingValue("None", "None", BitmapCompression.None),
new ChoiceSetting.ChoiceSettingValue("RLE8", "RLE8", BitmapCompression.RLE8),
new ChoiceSetting.ChoiceSettingValue("RLE4", "RLE4", BitmapCompression.RLE4),
new ChoiceSetting.ChoiceSettingValue("Bitfields", "Bitfields", BitmapCompression.Bitfields),
new ChoiceSetting.ChoiceSettingValue("JPEG", "JPEG", BitmapCompression.JPEG),
new ChoiceSetting.ChoiceSettingValue("PNG", "PNG", BitmapCompression.PNG)
}));
return dfr;
}
@ -75,6 +86,9 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
/// <value>The vertical resolution, in pixels-per-meter, of the target device for the bitmap.</value>
public int VerticalResolution { get; set; } = 0;
private const int DIV_5 = 31;
private const int DIV_6 = 63;
protected override void LoadInternal(ref ObjectModel objectModel)
{
PictureObjectModel pic = (objectModel as PictureObjectModel);
@ -262,10 +276,30 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
if (header.Compression == BitmapCompression.Bitfields)
{
// R5G6B5
// If the bV5Compression member of the
// BITMAPV5HEADER is BI_BITFIELDS, the bmiColors
// member contains three DWORD color masks that
// specify the red, green, and blue components,
// respectively, of each pixel. Each WORD in the
// bitmap array represents a single pixel.
short value = br.ReadInt16();
b = (byte)(8 * value.GetBits(0, 5));
g = (byte)(8 * value.GetBits(6, 6));
r = (byte)(8 * value.GetBits(11, 5));
b = (byte)(value & header.Bitfields[0]); // R
g = (byte)(value & header.Bitfields[1]); // R
r = (byte)(value & header.Bitfields[2]); // R
a = (byte)(value & header.Bitfields[3]); // R
/*
b = (byte)(value.GetBits(0, 5));
g = (byte)(value.GetBits(6, 5));
r = (byte)(value.GetBits(11, 5));
a = (byte)(value.GetBits(16, 1));
b = (byte)(((double)255 / DIV_5) * b);
g = (byte)(((double)255 / DIV_5) * g);
r = (byte)(((double)255 / DIV_5) * r);
*/
}
else
{
@ -276,6 +310,7 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
g = (byte)(8 * value.GetBits(5, 5));
r = (byte)(8 * value.GetBits(10, 5));
}
a = 255;
break;
}
case BitmapBitsPerPixel.TrueColor:
@ -314,7 +349,11 @@ namespace UniversalEditor.DataFormats.Multimedia.Picture.Microsoft.Bitmap
Color color = Color.FromRGBAByte(r, g, b, a);
pic.SetPixel(color, x, y);
}
br.Align(4);
if (PixelDepth != BitmapBitsPerPixel.DeepColor)
{
// br.Align(4);
}
}
}
protected override void SaveInternal(ObjectModel objectModel)

View File

@ -0,0 +1,30 @@
//
// HDRCompression.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.Multimedia.Picture.Radiance
{
public enum HDRCompression
{
None = 0,
RunLengthEncoding = 1,
AdaptiveRunLengthEncoding = 2
}
}

View File

@ -0,0 +1,176 @@
//
// HDRDataFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using MBS.Framework.Drawing;
using MBS.Framework.Settings;
using UniversalEditor.ObjectModels.Multimedia.Picture;
namespace UniversalEditor.DataFormats.Multimedia.Picture.Radiance
{
public class HDRDataFormat : DataFormat
{
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = base.MakeReferenceInternal();
_dfr.Capabilities.Add(typeof(PictureObjectModel), DataFormatCapabilities.All);
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new ChoiceSetting("Compression", "Compression", HDRCompression.None, new ChoiceSetting.ChoiceSettingValue[]
{
new ChoiceSetting.ChoiceSettingValue("None", "None", HDRCompression.None),
new ChoiceSetting.ChoiceSettingValue("RunLengthEncoding", "Run-length encoding (standard)", HDRCompression.RunLengthEncoding),
new ChoiceSetting.ChoiceSettingValue("AdaptiveRunLengthEncoding", "Run-length encoding (adaptive)", HDRCompression.AdaptiveRunLengthEncoding)
}));
}
return _dfr;
}
public HDRCompression Compression { get; set; } = HDRCompression.None;
protected override void LoadInternal(ref ObjectModel objectModel)
{
PictureObjectModel pic = (objectModel as PictureObjectModel);
if (pic == null)
throw new ObjectModelNotSupportedException();
// pixels are stored one byte each R,G,B plus one byte shared exponent
string signature = Accessor.Reader.ReadFixedLengthString(11);
if (!signature.Equals("#?RADIANCE\n"))
throw new InvalidDataFormatException("file does not begin with '#?RADIANCE\\n'");
while (!Accessor.Reader.EndOfStream)
{
string line = ReadLine(Accessor.Reader);
if (String.IsNullOrEmpty(line))
break;
string[] parts = line.Split(new char[] { '=' });
}
int xs = 0, ys = 0;
int ydir = 0, xdir = 0;
int width = 0, height = 0;
string args = ReadLine(Accessor.Reader);
string[] argsparts = args.Split(new char[] { ' ' });
for (int i = 0; i < argsparts.Length; i++)
{
if (argsparts[i].Equals("-Y"))
{
ydir = -1;
height = Int32.Parse(argsparts[i + 1]);
ys = height - 1;
i++;
}
else if (argsparts[i].Equals("+Y"))
{
ydir = +1;
height = Int32.Parse(argsparts[i + 1]);
ys = 0;
i++;
}
else if (argsparts[i].Equals("-X"))
{
xdir = -1;
width = Int32.Parse(argsparts[i + 1]);
xs = width - 1;
i++;
}
else if (argsparts[i].Equals("+X"))
{
xdir = +1;
width = Int32.Parse(argsparts[i + 1]);
xs = 0;
i++;
}
}
pic.Width = width;
pic.Height = height;
Color lastColor = Color.Empty; // for run-length encoding
for (int y = ys; ec(y, height, ydir); y += ydir)
{
for (int x = xs; ec(x, width, xdir); x += xdir)
{
byte rz = Accessor.Reader.ReadByte();
byte gz = Accessor.Reader.ReadByte();
byte bz = Accessor.Reader.ReadByte();
byte exponent = Accessor.Reader.ReadByte();
if (rz == 255 && gz == 255 && bz == 255)
{
// run length encoding, exponent is run count
for (int i = 0; i < exponent; i++)
{
pic.SetPixel(lastColor, x, y);
x++;
if (ec(x, width, xdir))
{
x = xs;
y++;
}
}
}
float r = 0, g = 0, b = 0;
if (exponent != 0)
{
float f = MBS.Framework.MathExtensions.ldexp(1.0f, exponent - (int)(128 + 8));
r = rz * f;
g = gz * f;
b = bz * f;
}
Color color = Color.FromRGBASingle(r, g, b);
pic.SetPixel(color, x, y);
lastColor = color;
}
}
}
private static bool ec(int val, int max, int pixelDir)
{
if (pixelDir == 1)
return (val < max);
return (val >= 0);
}
private static string ReadLine(IO.Reader reader)
{
string value = System.Text.Encoding.UTF8.GetString(reader.ReadUntil(new byte[] { 0x0A }));
reader.ReadByte(); // clear the 0x0A
return value;
}
protected override void SaveInternal(ObjectModel objectModel)
{
PictureObjectModel pic = (objectModel as PictureObjectModel);
if (pic == null)
throw new ObjectModelNotSupportedException();
}
}
}

View File

@ -55,16 +55,14 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
public List<Color> GenerateColorMap()
{
List<Color> colorMap = new List<MBS.Framework.Drawing.Color>();
for (int index = 0; index < bitmapData.Length; index += 4)
for (int x = 0; x < bitmapData.Length; x++)
{
byte a = bitmapData[index + 0];
byte r = bitmapData[index + 1];
byte g = bitmapData[index + 2];
byte b = bitmapData[index + 3];
Color color = Color.FromRGBAByte(a, r, g, b);
if (!colorMap.Contains(color))
colorMap.Add(color);
for (int y = 0; y < bitmapData[x].Length; y++)
{
Color color = bitmapData[x][y];
if (!colorMap.Contains(color))
colorMap.Add(color);
}
}
return colorMap;
}
@ -85,7 +83,7 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
SetPixel(color, (int)lastAddedLocation.X, (int)lastAddedLocation.Y);
lastAddedLocation.X++;
}
[System.Diagnostics.DebuggerNonUserCode]
public void SetPixel(Color color, int x, int y)
{
if (x >= mvarWidth || y >= mvarHeight)
@ -94,24 +92,16 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
}
int index = (x + (y * mvarWidth)) * 4;
bitmapData[index] = (byte)(color.R * 255);
bitmapData[index + 1] = (byte)(color.G * 255);
bitmapData[index + 2] = (byte)(color.B * 255);
bitmapData[index + 3] = (byte)(color.A * 255);
bitmapData[x][y] = color;
int realIndex = (int)(index / 4);
bitmapDataSet[realIndex] = true;
}
public void ClearPixel(int x, int y)
{
int index = (int)((lastAddedLocation.X + (lastAddedLocation.Y * mvarWidth)) * 4);
bitmapData[index] = 0;
bitmapData[index + 1] = 0;
bitmapData[index + 2] = 0;
bitmapData[index + 3] = 0;
bitmapData[x][y] = Color.Empty;
int realIndex = (int)(index / 4);
bitmapDataSet[realIndex] = false;
}
public Color GetPixel(int x, int y)
{
@ -122,14 +112,9 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
int index = (x + (y * mvarWidth)) * 4;
int realIndex = (int)(index / 4);
if (bitmapDataSet[realIndex])
if (!bitmapData[x][y].IsEmpty)
{
byte a = bitmapData[index + 0];
byte r = bitmapData[index + 1];
byte g = bitmapData[index + 2];
byte b = bitmapData[index + 3];
Color color = Color.FromRGBAByte(a, r, g, b);
Color color = bitmapData[x][y];
return color;
}
return Color.Empty;
@ -139,21 +124,32 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
return GetPixel((int)point.X, (int)point.Y);
}
private byte[] bitmapData = new byte[] { 0, 0, 0, 0 };
private bool[] bitmapDataSet = new bool[] { false };
private Color[][] bitmapData = new Color[0][];
public PictureObjectModel()
{
}
public PictureObjectModel(int width, int height)
{
bitmapData = new byte[(width * height) * 4];
bitmapDataSet = new bool[(width * height) * 4];
InitializeBitmapData();
mvarWidth = width;
mvarHeight = height;
}
private static void InitializeBitmapData(ref Color[][] array, int width, int height)
{
array = new Color[width][];
for (int i = 0; i < width; i++)
{
array[i] = new Color[height];
}
}
private void InitializeBitmapData()
{
InitializeBitmapData(ref bitmapData, Width, Height);
}
private int mvarWidth = 1;
public int Width
{
@ -161,8 +157,7 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
set
{
mvarWidth = value;
bitmapData = new byte[mvarWidth * mvarHeight * 4];
bitmapDataSet = new bool[mvarWidth * mvarHeight];
InitializeBitmapData();
}
}
private int mvarHeight = 1;
@ -172,8 +167,19 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
set
{
mvarHeight = value;
bitmapData = new byte[mvarWidth * mvarHeight * 4];
bitmapDataSet = new bool[mvarWidth * mvarHeight];
InitializeBitmapData();
}
}
public Dimension2D Size
{
get { return new Dimension2D(mvarWidth, mvarHeight); }
set
{
mvarWidth = (int)value.Width;
mvarHeight = (int)value.Height;
InitializeBitmapData();
}
}
@ -196,11 +202,10 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
PictureObjectModel clone = (destination as PictureObjectModel);
clone.Width = mvarWidth;
clone.Height = mvarHeight;
clone.bitmapData = (bitmapData.Clone() as byte[]);
clone.bitmapDataSet = (bitmapDataSet.Clone() as bool[]);
clone.bitmapData = (bitmapData.Clone() as Color[][]);
}
public byte[] ToByteArray()
public byte[] ToByteArray(PixelFormat format)
{
// memory goes from left to right, top to bottom
byte[] data = new byte[mvarWidth * mvarHeight * 4];
@ -211,14 +216,32 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
{
int index = (w + (h * mvarWidth)) * 4;
int realIndex = (int)(index / 4);
if (bitmapDataSet[realIndex])
switch (format)
{
data[i + 3] = bitmapData[index + 3];
data[i + 2] = bitmapData[index + 0];
data[i + 1] = bitmapData[index + 1];
data[i + 0] = bitmapData[index + 2];
case PixelFormat.BGRA:
{
// BGRA layout
data[i + 0] = bitmapData[w][h].GetBlueByte();
data[i + 1] = bitmapData[w][h].GetGreenByte();
data[i + 2] = bitmapData[w][h].GetRedByte();
data[i + 3] = bitmapData[w][h].GetAlphaByte();
i += 4;
break;
}
case PixelFormat.RGBA:
{
// RGBA layout
data[i + 0] = bitmapData[w][h].GetRedByte();
data[i + 1] = bitmapData[w][h].GetGreenByte();
data[i + 2] = bitmapData[w][h].GetBlueByte();
data[i + 3] = bitmapData[w][h].GetAlphaByte();
i += 4;
break;
}
}
i += 4;
}
}
return data;
@ -258,5 +281,93 @@ namespace UniversalEditor.ObjectModels.Multimedia.Picture
int stride = 4 * ((Width * bytesPerPixel + 3) / 4);
return stride;
}
public void Rotate(int degrees)
{
int oldheight = Height;
int oldwidth = Width;
// fuck this ain't workin
if (degrees % 360 == 0)
{
return;
}
switch (degrees)
{
case -90:
{
Color[][] oldpixels = bitmapData;
Size = new Dimension2D(Height, Width);
Color[][] pixels = null;
InitializeBitmapData(ref pixels, Width, Height);
// 1 2 3
// 4 5 6
// =>
// 3 6
// 2 5
// 1 4
int x1 = 0, y1 = 0;
for (int y = 0; y < oldheight; y++)
{
for (int x = oldwidth - 1; x >= 0; x--)
{
pixels[x1][y1] = oldpixels[x][y];
y1++;
}
y1 = 0;
x1++;
}
bitmapData = pixels;
break;
}
case 90:
{
Color[][] oldpixels = bitmapData;
Size = new Dimension2D(Height, Width);
Color[][] pixels = null;
InitializeBitmapData(ref pixels, Width, Height);
// 1 2 3
// 4 5 6
// =>
// 3 6
// 2 5
// 1 4
int x1 = 0, y1 = 0;
for (int y = 0; y < oldheight; y++)
{
for (int x = oldwidth - 1; x >= 0; x--)
{
pixels[x1][y1] = oldpixels[x][y];
y1++;
}
y1 = 0;
x1++;
}
bitmapData = pixels;
break;
}
case 180:
{
break;
}
case 270:
{
break;
}
}
}
}
}

View File

@ -0,0 +1,29 @@
//
// PixelFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2021 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.ObjectModels.Multimedia.Picture
{
public enum PixelFormat
{
BGRA,
RGBA
}
}

View File

@ -344,6 +344,16 @@
<Compile Include="DataFormats\Multimedia\Picture\KISS\CELDataFormat.cs" />
<Compile Include="DataFormats\Multimedia\Picture\KISS\CELImageType.cs" />
<Compile Include="DataFormats\Multimedia\Palette\KISS\KCFDataFormat.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRDataFormat.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRCompression.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRLineOrder.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRFlags.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRChannel.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRPixelType.cs" />
<Compile Include="DataFormats\Multimedia\Picture\EXR\EXRChromaticities.cs" />
<Compile Include="DataFormats\Multimedia\Picture\Radiance\HDRDataFormat.cs" />
<Compile Include="DataFormats\Multimedia\Picture\Radiance\HDRCompression.cs" />
<Compile Include="ObjectModels\Multimedia\Picture\PixelFormat.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Compression\UniversalEditor.Compression.csproj">
@ -406,6 +416,8 @@
<Folder Include="DataFormats\Multimedia\Palette\Adobe\SwatchExchange\" />
<Folder Include="DataFormats\Multimedia\Picture\KISS\" />
<Folder Include="DataFormats\Multimedia\Palette\KISS\" />
<Folder Include="DataFormats\Multimedia\Picture\EXR\" />
<Folder Include="DataFormats\Multimedia\Picture\Radiance\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Associations\Playlist\ASX.uexml" />
@ -481,6 +493,8 @@
<EmbeddedResource Include="Associations\Palette\RIFFPalette.uexml" />
<EmbeddedResource Include="Associations\Picture\KISS.uexml" />
<EmbeddedResource Include="Associations\Palette\KISS.uexml" />
<EmbeddedResource Include="Associations\Picture\EXR.uexml" />
<EmbeddedResource Include="Associations\Picture\Radiance.uexml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>