public byte[] LoadImage(string imagePath, Arranger arranger, ColorMatchStrategy matchStrategy) { using var inputImage = SixLabors.ImageSharp.Image.Load <Rgba32>(imagePath); var width = inputImage.Width; var height = inputImage.Height; var outputImage = new byte[width * height]; int destidx = 0; for (int y = 0; y < height; y++) { var span = inputImage.GetPixelRowSpan(y); for (int x = 0; x < width; x++, destidx++) { if (arranger.GetElementAtPixel(x, y) is ArrangerElement el) { var pal = el.Palette; var color = new ColorRgba32(span[x].PackedValue); var palIndex = pal.GetIndexByNativeColor(color, matchStrategy); outputImage[destidx] = palIndex; } } } return(outputImage); }
/// <summary> /// Fills the surrounding, contiguous color area with a new color /// </summary> /// <param name="x">x-coordinate to start at in pixel coordinates</param> /// <param name="y">y-coordinate to start at in pixel coordinates</param> /// <param name="fillIndex">Palette index to fill with</param> /// <returns>True if any pixels were modified</returns> public static bool FloodFill(this DirectImage image, int x, int y, ColorRgba32 fillColor) { bool isModified = false; var replaceColor = image.GetPixel(x, y); if (fillColor.Color == replaceColor.Color) { return(false); } var openNodes = new Stack <(int x, int y)>(); openNodes.Push((x, y)); while (openNodes.Count > 0) { var nodePosition = openNodes.Pop(); if (nodePosition.x >= 0 && nodePosition.x < image.Width && nodePosition.y >= 0 && nodePosition.y < image.Height) { var nodeColor = image.GetPixel(nodePosition.x, nodePosition.y); if (nodeColor.Color == replaceColor.Color) { isModified = true; image.SetPixel(nodePosition.x, nodePosition.y, fillColor); openNodes.Push((nodePosition.x - 1, nodePosition.y)); openNodes.Push((nodePosition.x + 1, nodePosition.y)); openNodes.Push((nodePosition.x, nodePosition.y - 1)); openNodes.Push((nodePosition.x, nodePosition.y + 1)); } } } return(isModified); }
public ColorBgr6 ToForeignColor(ColorRgba32 nc) { byte r = (byte)(nc.r / 85); byte b = (byte)(nc.b / 85); byte g = (byte)(nc.g / 85); return(new ColorBgr6(r, g, b)); }
public ColorBgr15 ToForeignColor(ColorRgba32 nc) { byte r = (byte)(nc.r >> 3); byte g = (byte)(nc.g >> 3); byte b = (byte)(nc.b >> 3); return(new ColorBgr15(r, g, b)); }
public ColorNes ToForeignColor(ColorRgba32 nc) { if (_nesPalette.TryGetIndexByNativeColor(nc, ColorMatchStrategy.Nearest, out var index)) { return((ColorNes)_nesPalette.GetForeignColor(index)); } throw new ArgumentException($"{nameof(ToForeignColor)} parameter (R: {nc.R}, G: {nc.G}, B: {nc.B}, A: {nc.A}) could not be matched in palette '{_nesPalette.Name}'"); }
public ColorBgr9 ToForeignColor(ColorRgba32 nc) { byte r = (byte)MathF.Round(nc.r / (255f / 7f)); byte g = (byte)MathF.Round(nc.g / (255f / 7f)); byte b = (byte)MathF.Round(nc.b / (255f / 7f)); return(new ColorBgr9(r, g, b)); }
public ColorAbgr16 ToForeignColor(ColorRgba32 nc) { byte r = (byte)(nc.r >> 3); byte g = (byte)(nc.g >> 3); byte b = (byte)(nc.b >> 3); byte a = (byte)(nc.a <= AlphaSemiTransparent ? 0 : 1); return(new ColorAbgr16(r, g, b, a)); }
public N64Rgba32Codec() { Width = DefaultWidth; Height = DefaultHeight; _foreignBuffer = new byte[(StorageSize + 7) / 8]; _nativeBuffer = new ColorRgba32[Height, Width]; _bitReader = BitStream.OpenRead(_foreignBuffer, StorageSize); }
public Rgb24TiledCodec(int width, int height) { Width = width; Height = height; _foreignBuffer = new byte[(StorageSize + 7) / 8]; _nativeBuffer = new ColorRgba32[Height, Width]; _bitReader = BitStream.OpenRead(_foreignBuffer, StorageSize); }
/// <summary> /// Tries to set a pixel's palette index at the specified pixel coordinate /// </summary> /// <param name="x">x-coordinate in pixel coordinates</param> /// <param name="y">y-coordinate in pixel coordinates</param> /// <param name="color">Palette index of color</param> /// <returns>True if set, false if not set</returns> public static MagitekResult TrySetPixel(this IndexedImage image, int x, int y, ColorRgba32 color) { var result = image.CanSetPixel(x, y, color); if (result.Value is MagitekResult.Success) { image.SetPixel(x, y, color); } return(result); }
/// <summary> /// Sets a pixel's palette index at the specified pixel coordinate /// </summary> /// <param name="x">x-coordinate in pixel coordinates</param> /// <param name="y">y-coordinate in pixel coordinates</param> /// <param name="color">Palette index of color</param> public static void SetPixel(this IndexedImage image, int x, int y, ColorRgba32 color) { var el = image.Arranger.GetElementAtPixel(x + image.Left, y + image.Top); if (el?.Palette is Palette pal) { var index = pal.GetIndexByNativeColor(color, ColorMatchStrategy.Exact); image.Image[x + image.Width * y] = index; } else { throw new InvalidOperationException($"{nameof(SetPixel)} cannot set pixel at ({x}, {y}) because there is no associated palette"); } }
public void ToNative_AsExpected(IColor32 fc, ColorRgba32 expected) { var colorFactory = new ColorFactory(); var actual = colorFactory.ToNative(fc); Assert.Multiple(() => { Assert.AreEqual(expected.Color, actual.Color, ".Color components not equal"); Assert.AreEqual(expected.R, actual.R, "Red components not equal"); Assert.AreEqual(expected.G, actual.G, "Green components not equal"); Assert.AreEqual(expected.B, actual.B, "Blue components not equal"); Assert.AreEqual(expected.A, actual.A, "Alpha components not equal"); }); }
public static void WritePAA(BinaryWriterEx writer, ColorRgba32[,] image, PAAType type = PAAType.UNDEFINED, PAAFlags flags = PAAFlags.InterpolatedAlpha) { var max = new ColorRgba32(0, 0, 0, 0); ulong sumR = 0; ulong sumG = 0; ulong sumB = 0; ulong sumA = 0; byte minA = 255; ulong totalPixels = 0; for (int y = 0; y < image.GetLength(0); ++y) { for (int x = 0; x < image.GetLength(1); ++x) { var color = image[y, x]; max.r = Math.Max(color.r, max.r); max.g = Math.Max(color.g, max.g); max.b = Math.Max(color.b, max.b); max.a = Math.Max(color.a, max.a); minA = Math.Min(color.a, minA); if (color.a > 128) { sumR += color.r; sumG += color.g; sumB += color.b; sumA += color.a; totalPixels++; } } } var avg = new ColorRgba32((byte)(sumR / totalPixels), (byte)(sumG / totalPixels), (byte)(sumB / totalPixels), (byte)(sumA / totalPixels)); if (type == PAAType.UNDEFINED) { if (minA == 255) { type = PAAType.DXT1; } else { type = PAAType.DXT5; } } WritePAA(writer, new ReadOnlyMemory2D <ColorRgba32>(image), max, avg, type, flags); }
public void ToForeignColor_Converts_Correctly(ColorRgba32 nc, ColorBgr15 expected, ColorModel colorModel) { var colorFactory = new ColorFactory(); var actual = colorFactory.ToForeign(nc, colorModel); if (actual is IColor32 actual32) { Assert.Multiple(() => { Assert.AreEqual(expected.Color, actual.Color, ".Color components not equal"); Assert.AreEqual(expected.R, actual32.R, "Red components not equal"); Assert.AreEqual(expected.G, actual32.G, "Green components not equal"); Assert.AreEqual(expected.B, actual32.B, "Blue components not equal"); Assert.AreEqual(expected.A, actual32.A, "Alpha components not equal"); }); } }
private static void Convert(string source, string target) { Console.WriteLine($"{source} -> {target}"); using (var paaStream = File.OpenRead(source)) { using (var img = Image.Load <Rgba32>(source)) { var targetPixels = new ColorRgba32[img.Height, img.Width]; for (int y = 0; y < img.Height; ++y) { for (int x = 0; x < img.Width; ++x) { var srcPixel = img[x, y]; targetPixels[y, x] = new ColorRgba32(srcPixel.R, srcPixel.G, srcPixel.B, srcPixel.A); } } PaaEncoder.WritePAA(target, targetPixels); } } }
/// <summary> /// Determines if a color can be set to an existing pixel's palette index at the specified pixel coordinate /// </summary> /// <param name="x">x-coordinate in pixel coordinates</param> /// <param name="y">y-coordinate in pixel coordinates</param> /// <param name="color">Palette index of color</param> public static MagitekResult CanSetPixel(this IndexedImage image, int x, int y, ColorRgba32 color) { if (x >= image.Width || y >= image.Height || x < 0 || y < 0) { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because because it is outside of the Arranger")); } var el = image.Arranger.GetElementAtPixel(x + image.Left, y + image.Top); if (el is ArrangerElement element) { if (!(element.Codec is IIndexedCodec)) { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because the element's codec is not an indexed color type")); } var pal = element.Palette; if (pal is null) { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because arranger '{image.Arranger.Name}' has no palette specified for the element")); } if (!pal.ContainsNativeColor(color)) { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because the palette '{pal.Name}' does not contain the native color ({color.R}, {color.G}, {color.B}, {color.A})")); } var index = pal.GetIndexByNativeColor(color, ColorMatchStrategy.Exact); if (index >= (1 << element.Codec.ColorDepth)) { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because the color is contained at an index outside of the codec's range")); } return(MagitekResult.SuccessResult); } else { return(new MagitekResult.Failed($"Cannot set pixel at ({x}, {y}) because the element is undefined")); } }
public MagitekResult TryLoadImage(string imagePath, Arranger arranger, ColorMatchStrategy matchStrategy, out byte[] image) { using var inputImage = SixLabors.ImageSharp.Image.Load <Rgba32>(imagePath); var width = inputImage.Width; var height = inputImage.Height; if (width != arranger.ArrangerPixelSize.Width || height != arranger.ArrangerPixelSize.Height) { image = default; return(new MagitekResult.Failed($"Arranger dimensions ({arranger.ArrangerPixelSize.Width}, {arranger.ArrangerPixelSize.Height})" + $" do not match image dimensions ({width}, {height})")); } image = new byte[width * height]; int destidx = 0; for (int y = 0; y < height; y++) { var span = inputImage.GetPixelRowSpan(y); for (int x = 0; x < width; x++, destidx++) { if (arranger.GetElementAtPixel(x, y) is ArrangerElement el) { var pal = el.Palette; var color = new ColorRgba32(span[x].PackedValue); if (pal.TryGetIndexByNativeColor(color, matchStrategy, out var palIndex)) { image[destidx] = palIndex; } else { return(new MagitekResult.Failed($"Could not match image color (R: {color.R}, G: {color.G}, B: {color.B}, A: {color.A}) within palette '{pal.Name}'")); } } } } return(MagitekResult.SuccessResult); }
public ColorRgba32[] LoadImage(string imagePath) { using var inputImage = SixLabors.ImageSharp.Image.Load <Rgba32>(imagePath); var width = inputImage.Width; var height = inputImage.Height; var outputImage = new ColorRgba32[width * height]; int destidx = 0; for (int y = 0; y < height; y++) { var span = inputImage.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { var color = new ColorRgba32(span[x].PackedValue); outputImage[destidx] = color; destidx++; } } return(outputImage); }
protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) { input.Flatten(scratchSurface); var pixels = new ColorRgba32[scratchSurface.Width, scratchSurface.Height]; unsafe { for (int y = 0; y < scratchSurface.Height; ++y) { var src = scratchSurface.GetRowPointer(y); for (int x = 0; x < scratchSurface.Width; ++x) { pixels[y, x] = new ColorRgba32((*src).R, (*src).G, (*src).B, (*src).A); src++; } } } using (var writer = new BinaryWriterEx(output, true)) { PaaEncoder.WritePAA(writer, pixels); } }
public ProjectNativeColorSource(ColorRgba32 value) { Value = value; }
public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) { var type = Bc7BlockType.Type5; Span <Bc7Block> outputs = stackalloc Bc7Block[4]; for (int rotation = 0; rotation < 4; rotation++) { var rotatedBlock = Bc7EncodingHelpers.RotateBlockColors(block, rotation); Bc7Block output = new Bc7Block(); Bc7EncodingHelpers.GetInitialUnscaledEndpoints(rotatedBlock, out var ep0, out var ep1); ColorRgba32 scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, false, out byte _); ColorRgba32 scaledEp1 = Bc7EncodingHelpers.ScaleDownEndpoint(ep1, type, false, out byte _); byte pBit = 0; //fake pBit Bc7EncodingHelpers.OptimizeSubsetEndpointsWithPBit(type, rotatedBlock, ref scaledEp0, ref scaledEp1, ref pBit, ref pBit, startingVariation, partitionTable, subset, false, true); ep0 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp0, 0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp1, 0); byte[] colorIndices = new byte[16]; byte[] alphaIndices = new byte[16]; Bc7EncodingHelpers.FillAlphaColorIndices(type, rotatedBlock, ep0, ep1, colorIndices, alphaIndices); bool needsRedo = false; if ((colorIndices[0] & 0b10) > 0) //If anchor index most significant bit is 1, switch endpoints { var c = scaledEp0; var alpha0 = scaledEp0.a; var alpha1 = scaledEp1.a; scaledEp0 = scaledEp1; scaledEp1 = c; scaledEp0.a = alpha0; scaledEp1.a = alpha1; needsRedo = true; } if ((alphaIndices[0] & 0b10) > 0) //If anchor index most significant bit is 1, switch endpoints { var a = scaledEp0.a; scaledEp0.a = scaledEp1.a; scaledEp1.a = a; needsRedo = true; } if (needsRedo) { //redo indices ep0 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp0, 0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp1, 0); Bc7EncodingHelpers.FillAlphaColorIndices(type, rotatedBlock, ep0, ep1, colorIndices, alphaIndices); } output.PackType5(rotation, new[] { new byte[] { scaledEp0.r, scaledEp0.g, scaledEp0.b }, new byte[] { scaledEp1.r, scaledEp1.g, scaledEp1.b }, }, new[] { scaledEp0.a, scaledEp1.a }, colorIndices, alphaIndices); outputs[rotation] = output; } int bestIndex = 0; float bestError = 0; bool first = true; // Find best out of generated blocks for (int i = 0; i < outputs.Length; i++) { var decoded = outputs[i].Decode(); float error = block.CalculateYCbCrAlphaError(decoded); if (error < bestError || first) { first = false; bestError = error; bestIndex = i; } } return(outputs[bestIndex]); }
public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation, int bestPartition) { Bc7Block output = new Bc7Block(); const Bc7BlockType type = Bc7BlockType.Type2; ColorRgba32[] endpoints = new ColorRgba32[6]; ReadOnlySpan <int> partitionTable = Bc7Block.Subsets3PartitionTable[bestPartition]; byte[] indices = new byte[16]; int[] anchorIndices = new int[] { 0, Bc7Block.Subsets3AnchorIndices2[bestPartition], Bc7Block.Subsets3AnchorIndices3[bestPartition] }; for (int subset = 0; subset < 3; subset++) { Bc7EncodingHelpers.GetInitialUnscaledEndpointsForSubset(block, out var ep0, out var ep1, partitionTable, subset); ColorRgba32 scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, type, true, out byte _); ColorRgba32 scaledEp1 = Bc7EncodingHelpers.ScaleDownEndpoint(ep1, type, true, out byte _); byte pBit = 0; Bc7EncodingHelpers.OptimizeSubsetEndpointsWithPBit(type, block, ref scaledEp0, ref scaledEp1, ref pBit, ref pBit, startingVariation, partitionTable, subset, false, false); ep0 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp0, 0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp1, 0); Bc7EncodingHelpers.FillSubsetIndices(type, block, ep0, ep1, partitionTable, subset, indices); if ((indices[anchorIndices[subset]] & 0b10) > 0) //If anchor index most significant bit is 1, switch endpoints { var c = scaledEp0; scaledEp0 = scaledEp1; scaledEp1 = c; //redo indices ep0 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp0, 0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(type, scaledEp1, 0); Bc7EncodingHelpers.FillSubsetIndices(type, block, ep0, ep1, partitionTable, subset, indices); } endpoints[subset * 2] = scaledEp0; endpoints[subset * 2 + 1] = scaledEp1; } output.PackType2(bestPartition, new[] { new byte[] { endpoints[0].r, endpoints[0].g, endpoints[0].b }, new byte[] { endpoints[1].r, endpoints[1].g, endpoints[1].b }, new byte[] { endpoints[2].r, endpoints[2].g, endpoints[2].b }, new byte[] { endpoints[3].r, endpoints[3].g, endpoints[3].b }, new byte[] { endpoints[4].r, endpoints[4].g, endpoints[4].b }, new byte[] { endpoints[5].r, endpoints[5].g, endpoints[5].b } }, indices); return(output); }
public static void WritePAA(BinaryWriterEx writer, ReadOnlyMemory2D <ColorRgba32> image, ColorRgba32 max, ColorRgba32 avg, PAAType type = PAAType.DXT5, PAAFlags flags = PAAFlags.InterpolatedAlpha) { if (type != PAAType.DXT5 && type != PAAType.DXT1) { throw new NotSupportedException(); } var enc = new BCnEncoder.Encoder.BcEncoder(type == PAAType.DXT5 ? CompressionFormat.Bc3 : CompressionFormat.Bc1); var mipmaps = new List <MipmapEncoder>(); var width = image.Width; var height = image.Width; var offset = type == PAAType.DXT5 ? 128 : 112; foreach (var mipmap in enc.EncodeToRawBytes(image)) { if (width > 2 || height > 2) { var menc = new MipmapEncoder(mipmap, width, height, offset); offset += menc.MipmapEntrySize; mipmaps.Add(menc); width = width / 2; height = height / 2; } } writer.Write(type == PAAType.DXT5 ? (ushort)0xff05 : (ushort)0xff01); writer.WriteAscii("GGATCGVA", 8); writer.Write((uint)4); writer.Write(avg.r); writer.Write(avg.g); writer.Write(avg.b); writer.Write(avg.a); writer.WriteAscii("GGATCXAM", 8); writer.Write((uint)4); writer.Write(max.r); writer.Write(max.g); writer.Write(max.b); writer.Write(max.a); if (type == PAAType.DXT5) { writer.WriteAscii("GGATGALF", 8); writer.Write((uint)4); writer.Write((uint)flags); } writer.WriteAscii("GGATSFFO", 8); writer.Write(16 * 4); for (int i = 0; i < 16; ++i) { if (i < mipmaps.Count) { writer.Write((uint)mipmaps[i].Offset); } else { writer.Write((uint)0); } } writer.Write((ushort)0x0000); int index = 0; foreach (var mipmap in mipmaps) { if (writer.Position != mipmap.Offset) { throw new Exception($"Wrong offset @{mipmap.Width} : {writer.Position} != { mipmap.Offset}"); } writer.Write(mipmap.WidthEncoded); writer.Write(mipmap.Height); writer.WriteUInt24((uint)mipmap.PaaData.Length); writer.Write(mipmap.PaaData); index++; width = width / 2; height = height / 2; } writer.Write((uint)0x0000); writer.Write((ushort)0x0000); }
public static Bc7Block EncodeBlock(RawBlock4X4Rgba32 block, int startingVariation) { bool hasAlpha = block.HasTransparentPixels(); Bc7Block output = new Bc7Block(); Bc7EncodingHelpers.GetInitialUnscaledEndpoints(block, out var ep0, out var ep1); ColorRgba32 scaledEp0 = Bc7EncodingHelpers.ScaleDownEndpoint(ep0, Bc7BlockType.Type6, false, out byte pBit0); ColorRgba32 scaledEp1 = Bc7EncodingHelpers.ScaleDownEndpoint(ep1, Bc7BlockType.Type6, false, out byte pBit1); ReadOnlySpan<int> partitionTable = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const int subset = 0; //Force 255 alpha if fully opaque if (!hasAlpha) { pBit0 = 1; pBit1 = 1; } Bc7EncodingHelpers.OptimizeSubsetEndpointsWithPBit(Bc7BlockType.Type6, block, ref scaledEp0, ref scaledEp1, ref pBit0, ref pBit1, startingVariation, partitionTable, subset, hasAlpha, hasAlpha); ep0 = Bc7EncodingHelpers.ExpandEndpoint(Bc7BlockType.Type6, scaledEp0, pBit0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(Bc7BlockType.Type6, scaledEp1, pBit1); byte[] indices = new byte[16]; Bc7EncodingHelpers.FillSubsetIndices(Bc7BlockType.Type6, block, ep0, ep1, partitionTable, subset, indices); if ((indices[0] & 0b1000) > 0) //If anchor index most significant bit is 1, switch endpoints { var c = scaledEp0; var p = pBit0; scaledEp0 = scaledEp1; pBit0 = pBit1; scaledEp1 = c; pBit1 = p; //redo indices ep0 = Bc7EncodingHelpers.ExpandEndpoint(Bc7BlockType.Type6, scaledEp0, pBit0); ep1 = Bc7EncodingHelpers.ExpandEndpoint(Bc7BlockType.Type6, scaledEp1, pBit1); Bc7EncodingHelpers.FillSubsetIndices(Bc7BlockType.Type6, block, ep0, ep1, partitionTable, subset, indices); } output.PackType6(new[]{ new byte[]{scaledEp0.r, scaledEp0.g, scaledEp0.b, scaledEp0.a}, new byte[]{scaledEp1.r, scaledEp1.g, scaledEp1.b, scaledEp1.a}, }, new[] { pBit0, pBit1 }, indices); return output; }